e in software.
    from amaranth.lib.crc import Algorithm
    algo = Algorithm(crc_width=16, polynomial=0x1021, initial_crc=0xffff,
                     reflect_input=False, reflect_output=False,
                     xor_output=0x0000)
    assert algo(data_width=8).compute(b"123456789") == 0x29b1
"""

from ... import *

__all__ = ["Algorithm", "Parameters", "Processor", "catalog"]


class Algorithm:
    """
    Settings for a CRC algorithm, excluding data width.

    The parameter set is based on the Williams model from
    "A Painless Guide to CRC Error Detection Algorithms":
    http://www.ross.net/crc/download/crc_v3.txt

    For a reference of standard CRC parameter sets, refer to:

    * `reveng`_'s catalogue, which uses an identical parameterisation,
    * `crcmod`_'s list of predefined functions, but remove the leading '1'
      from the polynominal, XOR the "Init-value" with "XOR-out" to obtain
      ``initial_crc``, and where "Reversed" is True, set both ``reflect_input``
      and ``reflect_output`` to True,
    * `CRC Zoo`_, which contains only polynomials; use the "explicit +1"
      form of polynomial but remove the leading '1'.

    .. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm
    .. _crcmod: http://crcmod.sourceforge.net/crcmod.predefined.html
    .. _CRC Zoo: https://users.ece.cmu.edu/~koopman/crc/

    Many commonly used CRC algorithms are available in the
    :py:mod:`~amaranth.lib.crc.catalog` module, which includes
    all entries in the `reveng`_ catalogue.

    To create a :py:class:`Parameters` instance, call the :py:class:`Algorithm`
    object with the required data width, which defaults to 8 bits.

    Parameters
    ----------
    crc_width : int
        Bit width of CRC word. Also known as "width" in the Williams model.
    polynomial : int
        CRC polynomial to use, ``crc_width`` bits long, without the implicit
        ``x**crc_width`` term. Polynomial is always specified with the highest
        order terms in the most significant bit positions; use
        ``reflect_input`` and ``reflect_output`` to perform a least
        significant bit first computation.
    initial_crc : int
        Initial value of CRC register at reset. Most significant bit always
        corresponds to the highest order term in the CRC register.
    reflect_input : bool
        If True, the input data words are bit-reflected, so that they are
        processed least significant bit first.
    reflect_output : bool
        If True, the output CRC is bit-reflected, so the least-significant bit
        of the output is the highest-order bit of the CRC register.
        Note that this reflection is performed over the entire CRC register;
        for transmission you may want to treat the output as a little-endian
        multi-word value, so for example the reflected 16-bit output 0x4E4C
        would be transmitted as the two octets 0x4C 0x4E, each transmitted
        least significant bit first.
    xor_output : int
        The output CRC will be the CRC register XOR'd with this value, applied
        after any output bit-reflection.
    """
    def __init__(self, *, crc_width, polynomial, initial_crc, reflect_input,
                 reflect_output, xor_output):
        self.crc_width = int(crc_width)
        self.polynomial = int(polynomial)
        self.initial_crc = int(initial_crc)
        self.reflect_input = bool(reflect_input)
        self.reflect_output = bool(reflect_output)
        self.xor_output = int(xor_output)

        if self.crc_width <= 0:
            raise ValueError("crc_width must be greater than 0")
        if not 0 <= self.polynomial < 2 ** self.crc_width:
            raise ValueError("polynomial must be between 0 and 2**crc_width - 1")
        if not 0 <= self.initial_crc < 2 ** self.crc_width:
            raise ValueError("initial_crc must be between 0 and 2**crc_width - 1")
        if not 0 <= self.xor_output < 2 ** self.crc_width:
            raise ValueError("xor_output must be between 0 and 2**crc_width - 1")

    def __call__(self, data_width=8):
        """
        Constructs a :py:class:`Parameters` instance from this
        :py:class:`Algorithm` with the specified ``data_width``.

        Parameters
        ----------
        data_width : int
            Bit width of data words, default 8.
        """
        return Parameters(self, data_width)

    def __repr__(self):
        return f"Algorithm(crc_width={self.crc_width}," \
               f" polynomial=0x{self.polynomial:0{self.crc_width//4}x}," \
               f" initial_crc=0x{self.initial_crc:0{self.crc_width//4}x}," \
               f" reflect_input={self.reflect_input}," \
               f" reflect_output={self.reflect_output}," \
               f" xor_output=0x{self.xor_output:0{self.crc_width//4}x})"


class Parameters:
    """
    Full set of parameters for a CRC computation.

    Contains the settings from :py:class:`Algorithm` and additionally
    ``data_width``. Refer to :py:class:`Algorithm` for details of what each
    parameter means and how to construct them.

    From this class, you can directly compute CRCs with the
    :py:meth:`~Parameters.compute` method, or construct a hardware module with
    the :py:meth:`~Parameters.create` method.

    Parameters
    ----------
    algorithm : Algorithm
        CRC algorithm to use. Specifies the CRC width, polynomial,
        initial value, whether to reflect the input or output words,
        and any output XOR.
    data_width : int
        Bit width of data words.
    """
    def __init__(self, algorithm, data_width=8):
        self._crc_width = algorithm.crc_width
        self._polynomial = algorithm.polynomial
        self._initial_crc = algorithm.initial_crc
        self._reflect_input = algorithm.reflect_input
        self._reflect_output = algorithm.reflect_output
        self._xor_output = algorithm.xor_output
        self._data_width = int(data_width)
        if self._data_width <= 0:
            raise ValueError("data_width must be greater than 0")

    @property
    def algorithm(self):
        """
        Returns an :py:class:`Algorithm` with the CRC settings from this
        instance.
        """
        return Algorithm(
            crc_width=self._crc_width,
            polynomial=self._polynomial,
            initial_crc=self._initial_crc,
            reflect_input=self._reflect_input,
            reflect_output=self._reflect_output,
            xor_output=self._xor_output)

    def residue(self):
        """
        Compute the residue value for this CRC, which is the value left in the
        CRC register after processing any valid codeword.
        """
        # Residue is computed by initialising to (possibly reflected)
        # xor_output, feeding crc_width worth of 0 bits, then taking
        # the (possibly reflected) output without any XOR.
        if self._reflect_output:
            init = self._reflect(self._xor_output, self._crc_width)
        else:
            init = self._xor_output
        algo = self.algorithm
        algo.initial_crc = init
        algo.reflect_input = False
        algo.xor_output = 0
        return algo(data_width=self._crc_width).compute([0])

    def create(self):
        """
        Returns a ``Processor`` configured with these parameters.
        """
        return Processor(self)

    def compute(self, data):
        """
        Computes and returns the CRC of all data words in ``data``.

        Parameters
        ----------
        data : iterable of integers
            The CRC is computed over this complete set of data.
            Each item is an integer of bitwidth equal to ``data_width``.
        """
        # Precompute some constants we use every iteration.
        word_max = (1 << self._data_width) - 1
        top_bit = 1 << (self._crc_width + self._data_width - 1)
        crc_mask = (1 << (self._crc_width + self._data_width)) - 1
        poly_shifted = self._polynomial << self._data_width

        # Implementation notes:
        # We always compute most-significant bit first, which means the
        # polynomial and initial value may be used as-is, and the reflect_in
        # and reflect_out values have their usual sense.
        # However, when computing word-at-a-time and MSbit-first, we must align
        # the input word so its MSbit lines up with the MSbit of the previous
        # CRC value. When the CRC width is smaller than the word width, this
        # would normally truncate data bits.
        # Instead, we shift the initial CRC left by the data width, and the
        # data word left by the crc width, lining up their MSbits no matter
        # the relation between the two widths.
        # The new CRC is then shifted right by the data width before output.

        crc = self._initial_crc << self._data_width
        for word in data:
            if not 0 <= word <= word_max:
                raise ValueError("data word must be between 0 and {}".format(word_max - 1))

            if self._reflect_input:
                word = self._reflect(word, self._data_width)

            crc ^= word << self._crc_width
            for _ in range(self._data_width):
                if crc & top_bit:
                    crc = (crc << 1) ^ poly_shifted
                else:
                    crc <<= 1
            crc &= crc_mask

        crc >>= self._data_width
        if self._reflect_output:
            crc = self._reflect(crc, self._crc_width)

        crc ^= self._xor_output
        return crc

    @staticmethod
    def _reflect(word, n):
        """
        Bitwise-reflects an n-bit word ``word``.
        """
        return int(f"{word:0{n}b}"[::-1], 2)

    def _matrices(self):
        """
        Computes the F and G matrices for parallel CRC computation, treating
        the CRC as a linear time-invariant system described by the state
        relation x(t+1) = F.x(i) + G.u(i), where x(i) and u(i) are column
        vectors of the bits of the CRC register and input word, F is the n-by-n
        matrix relating the old state to the new state, and G is the n-by-m
        matrix relating the new data to the new state, where n is the CRC
        width and m is the data word width.

        The matrices are ordered least-significant-bit first; in other words
        the first entry, with index (0, 0), corresponds to the effect of the
        least-significant bit of the input on the least-significant bit of the
        output.

        For convenience of implementation, both matrices are returned
        transposed: the first index is the input bit, and the second index is
        the corresponding output bit.

        The matrices are used to select which bits are XORd together to compute
        each bit i of the new state: if F[j][i] is set then bit j of the old
        state is included in the XOR, and if G[j][i] is set then bit j of the
        new data is included.

        These matrices are not affected by ``initial_crc``, ``reflect_input``,
        ``reflect_output``, or ``xor_output``.
        """
        f = []
        g = []
        algo = self.algorithm
        algo.reflect_input = algo.reflect_output = False
        algo.xor_output = 0
        crc = Parameters(algo, self._data_width)
        for i in range(self._crc_width):
            crc._initial_crc = 2 ** i
            w = crc.compute([0])
            f.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
        for i in range(self._data_width):
            crc._initial_crc = 0
            w = crc.compute([2 ** i])
            g.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
        return f, g

    def __repr__(self):
        return f"Parameters({self.algorithm!r}, data_width={self._data_width})"


class Processor(Elaboratable):
    """
    Cyclic redundancy check (CRC) processor module.

    This module generates CRCs from an input data stream, which can be used
    to validate an existing CRC or generate a new CRC. It is configured by
    the :py:class:`Parameters` class, which can handle most forms of CRCs.
    Refer to that class's documentation for a description of the parameters.

    The CRC value is updated on any clock cycle where ``valid`` is asserted,
    with the updated value available on the ``crc`` output on the subsequent
    clock cycle. The latency is therefore one clock cycle, and the throughput
    is one data word per clock cycle.

    The CRC is reset to its initial value whenever ``start`` is asserted.
    ``start`` and ``valid`` may be asserted on the same clock cycle, in which
    case a new CRC computation is started with the current value of ``data``.

    With ``data_width=1``, a classic bit-serial CRC is implemented for the
    given polynomial in a Galois-type shift register. For larger values of
    ``data_width``, a similar architecture computes every new bit of the
    CRC in parallel.

    The ``match_detected`` output may be used to validate data with a trailing
    CRC (also known as a codeword). If the most recently processed word(s) form
    the valid CRC of all the previous data since ``start`` was asserted, the
    CRC register will always take on a fixed value known as the residue.  The
    ``match_detected`` output indicates whether the CRC register currently
    contains this residue.

    Parameters
    ----------
    parameters : Parameters
        CRC parameters.

    Attributes
    ----------
    start : Signal(), in
        Assert to indicate the start of a CRC computation, re-initialising
        the CRC register to the initial value. May be asserted simultaneously
        with ``valid`` or by itself.
    data : Signal(data_width), in
        Data word to add to CRC when ``valid`` is asserted.
    valid : Signal(), in
        Assert when ``data`` is valid to add the data word to the CRC.
    crc : Signal(crc_width), out
        Registered CRC output value, updated one clock cycle after ``valid``
        becomes asserted.
    match_detected : Signal(), out
        Asserted if the current CRC value indicates a valid codeword has been
        received.
    """
    def __init__(self, parameters):
        if not isinstance(parameters, Parameters):
            raise TypeError("Algorithmn parameters must be of type Parameters, "
                            "not {!r}"
                            .format(parameters))
        self._crc_width = parameters._crc_width
        self._data_width = parameters._data_width
        self._polynomial = parameters._polynomial
        self._initial_crc = Const(parameters._initial_crc, self._crc_width)
        self._reflect_input = parameters._reflect_input
        self._reflect_output = parameters._reflect_output
        self._xor_output = parameters._xor_output
        self._matrix_f, self._matrix_g = parameters._matrices()
        self._residue = parameters.residue()

        self.start = Signal()
        self.data = Signal(self._data_width)
        self.valid = Signal()
        self.crc = Signal(self._crc_width)
        self.match_detected = Signal()

    def elaborate(self, platform):
        m = Module()

        crc_reg = Signal(self._crc_width, reset=self._initial_crc.value)
        data_in = Signal(self._data_width)

        # Optionally bit-reflect input words.
        if self._reflect_input:
            m.d.comb += data_in.eq(self.data[::-1])
        else:
            m.d.comb += data_in.eq(self.data)

        # Optionally bit-reflect and then XOR the output.
        if self._reflect_output:
            m.d.comb += self.crc.eq(crc_reg[::-1] ^ self._xor_output)
        else:
            m.d.comb += self.crc.eq(crc_reg ^ self._xor_output)

        # Compute next CRC state.
        source = Mux(self.start, self._initial_crc, crc_reg)
        with m.If(self.valid):
            for i in range(self._crc_width):
                bit = 0
                for j in range(self._crc_width):
                    if self._matrix_f[j][i]:
                        bit ^= source[j]
                for j in range(self._data_width):
                    if self._matrix_g[j][i]:
                        bit ^= data_in[j]
                m.d.sync += crc_reg[i].eq(bit)
        with m.Elif(self.start):
            m.d.sync += crc_reg.eq(self._initial_crc)

        # Check for residue match, indicating a valid codeword.
        if self._reflect_output:
            m.d.comb += self.match_detected.eq(crc_reg[::-1] == self._residue)
        else:
            m.d.comb += self.match_detected.eq(crc_reg == self._residue)

        return m


# Imported after Algorithm is defined to prevent circular imports.
from . import catalog


# amaranth/lib/crc/catalog.py

"""
This module contains a catalog of predefined CRC algorithms.

All entries are from `reveng`_, accessed on 2023-05-25.

    .. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm

To use an entry, call it with an optional ``data_width`` which defaults
to 8. For example:

.. code-block::

    crc8 = m.submodules.crc8 = crc.catalog.CRC8_AUTOSAR().create()

"""

from . import Algorithm

# Note: The trailing `#:` gives Sphinx an empty documentation string for each
# constant, allowing it to be documented with `automodule` (which otherwise
# ignores undocumented module constants) and also preventing it from using
# the Algorithm docstring if otherwise forced to document the constants.

CRC3_GSM = Algorithm(
    crc_width=3,
    polynomial=0x3,
    initial_crc=0x0,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x7)  #:

CRC3_ROHC = Algorithm(
    crc_width=3,
    polynomial=0x3,
    initial_crc=0x7,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0)  #:

CRC4_G_704 = CRC4_ITU = Algorithm(
    crc_width=4,
    polynomial=0x3,
    initial_crc=0x0,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0)  #:

CRC4_INTERLAKEN = Algorithm(
    crc_width=4,
    polynomial=0x3,
    initial_crc=0xf,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xf)  #:

CRC5_EPC_C1G2 = CRC5_EPC = Algorithm(
    crc_width=5,
    polynomial=0x09,
    initial_crc=0x09,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC5_G_704 = CRC5_ITU = Algorithm(
    crc_width=5,
    polynomial=0x15,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC5_USB = Algorithm(
    crc_width=5,
    polynomial=0x05,
    initial_crc=0x1f,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x1f)  #:

CRC6_CDMA2000_A = Algorithm(
    crc_width=6,
    polynomial=0x27,
    initial_crc=0x3f,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC6_CDMA2000_B = Algorithm(
    crc_width=6,
    polynomial=0x07,
    initial_crc=0x3f,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC6_DARC = Algorithm(
    crc_width=6,
    polynomial=0x19,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC6_G_704 = CRC6_ITU = Algorithm(
    crc_width=6,
    polynomial=0x03,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC6_GSM = Algorithm(
    crc_width=6,
    polynomial=0x2f,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x3f)  #:

CRC7_MMC = Algorithm(
    crc_width=7,
    polynomial=0x09,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC7_ROHC = Algorithm(
    crc_width=7,
    polynomial=0x4f,
    initial_crc=0x7f,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC7_UMTS = Algorithm(
    crc_width=7,
    polynomial=0x45,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_AUTOSAR = Algorithm(
    crc_width=8,
    polynomial=0x2f,
    initial_crc=0xff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xff)  #:

CRC8_BLUETOOTH = Algorithm(
    crc_width=8,
    polynomial=0xa7,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC8_CDMA2000 = Algorithm(
    crc_width=8,
    polynomial=0x9b,
    initial_crc=0xff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_DARC = Algorithm(
    crc_width=8,
    polynomial=0x39,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC8_DVB_S2 = Algorithm(
    crc_width=8,
    polynomial=0xd5,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_GSM_A = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_GSM_B = Algorithm(
    crc_width=8,
    polynomial=0x49,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xff)  #:

CRC8_HITAG = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0xff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_I_432_1 = CRC8_ITU = Algorithm(
    crc_width=8,
    polynomial=0x07,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x55)  #:

CRC8_I_CODE = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0xfd,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_LTE = Algorithm(
    crc_width=8,
    polynomial=0x9b,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_MAXIM_DOW = CRC8_MAXIM = Algorithm(
    crc_width=8,
    polynomial=0x31,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC8_MIFARE_MAD = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0xc7,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_NRSC_5 = Algorithm(
    crc_width=8,
    polynomial=0x31,
    initial_crc=0xff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_OPENSAFETY = Algorithm(
    crc_width=8,
    polynomial=0x2f,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_ROHC = Algorithm(
    crc_width=8,
    polynomial=0x07,
    initial_crc=0xff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC8_SAE_J1850 = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0xff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xff)  #:

CRC8_SMBUS = Algorithm(
    crc_width=8,
    polynomial=0x07,
    initial_crc=0x00,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00)  #:

CRC8_TECH_3250 = CRC8_AES = CRC8_ETU = Algorithm(
    crc_width=8,
    polynomial=0x1d,
    initial_crc=0xff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC8_WCDMA = Algorithm(
    crc_width=8,
    polynomial=0x9b,
    initial_crc=0x00,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00)  #:

CRC10_ATM = CRC10_I_610 = Algorithm(
    crc_width=10,
    polynomial=0x233,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC10_CDMA2000 = Algorithm(
    crc_width=10,
    polynomial=0x3d9,
    initial_crc=0x3ff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC10_GSM = Algorithm(
    crc_width=10,
    polynomial=0x175,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x3ff)  #:

CRC11_FLEXRAY = Algorithm(
    crc_width=11,
    polynomial=0x385,
    initial_crc=0x01a,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC11_UMTS = Algorithm(
    crc_width=11,
    polynomial=0x307,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC12_CDMA2000 = Algorithm(
    crc_width=12,
    polynomial=0xf13,
    initial_crc=0xfff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC12_DECT = Algorithm(
    crc_width=12,
    polynomial=0x80f,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000)  #:

CRC12_GSM = Algorithm(
    crc_width=12,
    polynomial=0xd31,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xfff)  #:

CRC12_UMTS = CRC12_3GPP = Algorithm(
    crc_width=12,
    polynomial=0x80f,
    initial_crc=0x000,
    reflect_input=False,
    reflect_output=True,
    xor_output=0x000)  #:

CRC13_BBC = Algorithm(
    crc_width=13,
    polynomial=0x1cf5,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC14_DARC = Algorithm(
    crc_width=14,
    polynomial=0x0805,
    initial_crc=0x0000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC14_GSM = Algorithm(
    crc_width=14,
    polynomial=0x202d,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x3fff)  #:

CRC15_CAN = Algorithm(
    crc_width=15,
    polynomial=0x4599,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC15_MPT1327 = Algorithm(
    crc_width=15,
    polynomial=0x6815,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0001)  #:

CRC16_ARC = CRC16_IBM = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0x0000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_CDMA2000 = Algorithm(
    crc_width=16,
    polynomial=0xc867,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_CMS = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_DDS_110 = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0x800d,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_DECT_R = Algorithm(
    crc_width=16,
    polynomial=0x0589,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0001)  #:

CRC16_DECT_X = Algorithm(
    crc_width=16,
    polynomial=0x0589,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_DNP = Algorithm(
    crc_width=16,
    polynomial=0x3d65,
    initial_crc=0x0000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffff)  #:

CRC16_EN_13757 = Algorithm(
    crc_width=16,
    polynomial=0x3d65,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffff)  #:

CRC16_GENIBUS = CRC16_DARC = CRC16_EPC = CRC16_EPC_C1G2 = CRC16_I_CODE = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffff)  #:

CRC16_GSM = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffff)  #:

CRC16_IBM_3740 = CRC16_AUTOSAR = CRC16_CCITT_FALSE = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_IBM_SDLC = CRC16_ISO_HDLC = CRC16_ISO_IEC_14443_3_B = CRC16_X25 = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffff)  #:

CRC16_ISO_IEC_14443_3_A = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xc6c6,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_KERMIT = CRC16_BLUETOOTH = CRC16_CCITT = CRC16_CCITT_TRUE = CRC16_V_41_LSB = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0x0000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_LJ1200 = Algorithm(
    crc_width=16,
    polynomial=0x6f63,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_M17 = Algorithm(
    crc_width=16,
    polynomial=0x5935,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_MAXIM_DOW = CRC16_MAXIM = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0x0000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffff)  #:

CRC16_MCRF4XX = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_MODBUS = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0xffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_NRSC_5 = Algorithm(
    crc_width=16,
    polynomial=0x080b,
    initial_crc=0xffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_OPENSAFETY_A = Algorithm(
    crc_width=16,
    polynomial=0x5935,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_OPENSAFETY_B = Algorithm(
    crc_width=16,
    polynomial=0x755b,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_PROFIBUS = CRC16_IEC_61158_2 = Algorithm(
    crc_width=16,
    polynomial=0x1dcf,
    initial_crc=0xffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffff)  #:

CRC16_RIELLO = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0xb2aa,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_SPI_FUJITSU = CRC16_AUG_CCITT = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0x1d0f,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_T10_DIF = Algorithm(
    crc_width=16,
    polynomial=0x8bb7,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_TELEDISK = Algorithm(
    crc_width=16,
    polynomial=0xa097,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_TMS37157 = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0x89ec,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000)  #:

CRC16_UMTS = CRC16_BUYPASS = CRC16_VERIFONE = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC16_USB = Algorithm(
    crc_width=16,
    polynomial=0x8005,
    initial_crc=0xffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffff)  #:

CRC16_XMODEM = CRC16_ACORN = CRC16_LTE = CRC16_V_41_MSB = CRC16_ZMODEM = Algorithm(
    crc_width=16,
    polynomial=0x1021,
    initial_crc=0x0000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000)  #:

CRC17_CAN_FD = Algorithm(
    crc_width=17,
    polynomial=0x1685b,
    initial_crc=0x00000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00000)  #:

CRC21_CAN_FD = Algorithm(
    crc_width=21,
    polynomial=0x102899,
    initial_crc=0x000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_BLE = Algorithm(
    crc_width=24,
    polynomial=0x00065b,
    initial_crc=0x555555,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x000000)  #:

CRC24_FLEXRAY_A = Algorithm(
    crc_width=24,
    polynomial=0x5d6dcb,
    initial_crc=0xfedcba,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_FLEXRAY_B = Algorithm(
    crc_width=24,
    polynomial=0x5d6dcb,
    initial_crc=0xabcdef,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_INTERLAKEN = Algorithm(
    crc_width=24,
    polynomial=0x328b63,
    initial_crc=0xffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffff)  #:

CRC24_LTE_A = Algorithm(
    crc_width=24,
    polynomial=0x864cfb,
    initial_crc=0x000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_LTE_B = Algorithm(
    crc_width=24,
    polynomial=0x800063,
    initial_crc=0x000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_OPENPGP = Algorithm(
    crc_width=24,
    polynomial=0x864cfb,
    initial_crc=0xb704ce,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x000000)  #:

CRC24_OS_9 = Algorithm(
    crc_width=24,
    polynomial=0x800063,
    initial_crc=0xffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffff)  #:

CRC30_CDMA = Algorithm(
    crc_width=30,
    polynomial=0x2030b9c7,
    initial_crc=0x3fffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x3fffffff)  #:

CRC31_PHILIPS = Algorithm(
    crc_width=31,
    polynomial=0x04c11db7,
    initial_crc=0x7fffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x7fffffff)  #:

CRC32_AIXM = Algorithm(
    crc_width=32,
    polynomial=0x814141ab,
    initial_crc=0x00000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00000000)  #:

CRC32_AUTOSAR = Algorithm(
    crc_width=32,
    polynomial=0xf4acfb13,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffff)  #:

CRC32_BASE91_D = Algorithm(
    crc_width=32,
    polynomial=0xa833982b,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffff)  #:

CRC32_BZIP2 = CRC32_AAL5 = CRC32_DECT_B = Algorithm(
    crc_width=32,
    polynomial=0x04c11db7,
    initial_crc=0xffffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffffff)  #:

CRC32_CD_ROM_EDC = Algorithm(
    crc_width=32,
    polynomial=0x8001801b,
    initial_crc=0x00000000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00000000)  #:

CRC32_CKSUM = CRC32_POSIX = Algorithm(
    crc_width=32,
    polynomial=0x04c11db7,
    initial_crc=0x00000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffffff)  #:

CRC32_ISCSI = CRC32_BASE91_C = CRC32_CASTAGNOLI = CRC32_INTERLAKEN = Algorithm(
    crc_width=32,
    polynomial=0x1edc6f41,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffff)  #:

CRC32_ISO_HDLC = CRC32_ADCCP = CRC32_V_42 = CRC32_XZ = CRC32_PKZIP = CRC32_ETHERNET = Algorithm(
    crc_width=32,
    polynomial=0x04c11db7,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffff)  #:

CRC32_JAMCRC = Algorithm(
    crc_width=32,
    polynomial=0x04c11db7,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00000000)  #:

CRC32_MEF = Algorithm(
    crc_width=32,
    polynomial=0x741b8cd7,
    initial_crc=0xffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x00000000)  #:

CRC32_MPEG_2 = Algorithm(
    crc_width=32,
    polynomial=0x04c11db7,
    initial_crc=0xffffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00000000)  #:

CRC32_XFER = Algorithm(
    crc_width=32,
    polynomial=0x000000af,
    initial_crc=0x00000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x00000000)  #:

CRC40_GSM = Algorithm(
    crc_width=40,
    polynomial=0x0004820009,
    initial_crc=0x0000000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffffffff)  #:

CRC64_ECMA_182 = Algorithm(
    crc_width=64,
    polynomial=0x42f0e1eba9ea3693,
    initial_crc=0x0000000000000000,
    reflect_input=False,
    reflect_output=False,
    xor_output=0x0000000000000000)  #:

CRC64_GO_ISO = Algorithm(
    crc_width=64,
    polynomial=0x000000000000001b,
    initial_crc=0xffffffffffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffffffffffff)  #:

CRC64_MS = Algorithm(
    crc_width=64,
    polynomial=0x259c84cba6426349,
    initial_crc=0xffffffffffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000000000000000)  #:

CRC64_REDIS = Algorithm(
    crc_width=64,
    polynomial=0xad93d23594c935a9,
    initial_crc=0x0000000000000000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x0000000000000000)  #:

CRC64_WE = Algorithm(
    crc_width=64,
    polynomial=0x42f0e1eba9ea3693,
    initial_crc=0xffffffffffffffff,
    reflect_input=False,
    reflect_output=False,
    xor_output=0xffffffffffffffff)  #:

CRC64_XZ = CRC64_ECMA = Algorithm(
    crc_width=64,
    polynomial=0x42f0e1eba9ea3693,
    initial_crc=0xffffffffffffffff,
    reflect_input=True,
    reflect_output=True,
    xor_output=0xffffffffffffffff)  #:

CRC82_DARC = Algorithm(
    crc_width=82,
    polynomial=0x0308c0111011401440411,
    initial_crc=0x000000000000000000000,
    reflect_input=True,
    reflect_output=True,
    xor_output=0x000000000000000000000)  #:


# docs/conf.py

import os, sys
sys.path.insert(0, os.path.abspath("."))

import amaranth

project = "Amaranth HDL toolchain"
version = amaranth.__version__
release = version.split("+")[0]
copyright = "2020—2023, Amaranth HDL developers"

extensions = [
	"sphinx.ext.intersphinx",
	"sphinx.ext.doctest",
    "sphinx.ext.todo",
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx_rtd_theme",
    "sphinxcontrib.platformpicker",
]

with open(".gitignore") as f:
    exclude_patterns = [line.strip() for line in f.readlines()]

root_doc = "cover"

intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}

todo_include_todos = True

autodoc_member_order = "bysource"
autodoc_default_options = {
    "members": True
}
autodoc_preserve_defaults = True

napoleon_google_docstring = False
napoleon_numpy_docstring = True
napoleon_use_ivar = True
napoleon_include_init_with_doc = True
napoleon_include_special_with_doc = True
napoleon_custom_sections = ["Platform overrides"]

html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
html_css_files = ["custom.css"]


# tests/test_build_dsl.py

from collections import OrderedDict

from amaranth.build.dsl import *

from .utils import *


class PinsTestCase(FHDLTestCase):
    def test_basic(self):
        p = Pins("A0 A1 A2")
        self.assertEqual(repr(p), "(pins io A0 A1 A2)")
        self.assertEqual(len(p.names), 3)
        self.assertEqual(p.dir, "io")
        self.assertEqual(p.invert, False)
        self.assertEqual(list(p), ["A0", "A1", "A2"])

    def test_invert(self):
        p = PinsN("A0")
        self.assertEqual(repr(p), "(pins-n io A0)")
        self.assertEqual(p.invert, True)

    def test_invert_arg(self):
        p = Pins("A0", invert=True)
        self.assertEqual(p.invert, True)

    def test_conn(self):
        p = Pins("0 1 2", conn=("pmod", 0))
        self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"])
        p = Pins("0 1 2", conn=("pmod", "a"))
        self.assertEqual(list(p), ["pmod_a:0", "pmod_a:1", "pmod_a:2"])

    def test_map_names(self):
        p = Pins("0 1 2", conn=("pmod", 0))
        mapping = {
            "pmod_0:0": "A0",
            "pmod_0:1": "A1",
            "pmod_0:2": "A2",
        }
        self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"])

    def test_map_names_recur(self):
        p = Pins("0", conn=("pmod", 0))
        mapping = {
            "pmod_0:0": "ext_0:1",
            "ext_0:1":  "A1",
        }
        self.assertEqual(p.map_names(mapping, p), ["A1"])

    def test_wrong_names(self):
        with self.assertRaisesRegex(TypeError,
                r"^Names must be a whitespace-separated string, not \['A0', 'A1', 'A2'\]$"):
            p = Pins(["A0", "A1", "A2"])

    def test_wrong_dir(self):
        with self.assertRaisesRegex(TypeError,
                r"^Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'$"):
            p = Pins("A0 A1", dir="wrong")

    def test_wrong_conn(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Connector must be None or a pair of string \(connector name\) and "
                    r"integer\/string \(connector number\), not \('foo', None\)$")):
            p = Pins("A0 A1", conn=("foo", None))

    def test_wrong_map_names(self):
        p = Pins("0 1 2", conn=("pmod", 0))
        mapping = {
            "pmod_0:0": "A0",
        }
        with self.assertRaisesRegex(NameError,
                (r"^Resource \(pins io pmod_0:0 pmod_0:1 pmod_0:2\) refers to nonexistent "
                    r"connector pin pmod_0:1$")):
            p.map_names(mapping, p)

    def test_wrong_assert_width(self):
        with self.assertRaisesRegex(AssertionError,
                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
            Pins("0 1 2", assert_width=4)


class DiffPairsTestCase(FHDLTestCase):
    def test_basic(self):
        dp = DiffPairs(p="A0 A1", n="B0 B1")
        self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))")
        self.assertEqual(dp.p.names, ["A0", "A1"])
        self.assertEqual(dp.n.names, ["B0", "B1"])
        self.assertEqual(dp.dir, "io")
        self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")])

    def test_invert(self):
        dp = DiffPairsN(p="A0", n="B0")
        self.assertEqual(repr(dp), "(diffpairs-n io (p A0) (n B0))")
        self.assertEqual(dp.p.names, ["A0"])
        self.assertEqual(dp.n.names, ["B0"])
        self.assertEqual(dp.invert, True)

    def test_invert_arg(self):
        dp = DiffPairs(p="A0", n="B0", invert=True)
        self.assertEqual(dp.invert, True)

    def test_conn(self):
        dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0))
        self.assertEqual(list(dp), [
            ("pmod_0:0", "pmod_0:3"),
            ("pmod_0:1", "pmod_0:4"),
            ("pmod_0:2", "pmod_0:5"),
        ])

    def test_dir(self):
        dp = DiffPairs("A0", "B0", dir="o")
        self.assertEqual(dp.dir, "o")
        self.assertEqual(dp.p.dir, "o")
        self.assertEqual(dp.n.dir, "o")

    def test_wrong_width(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Positive and negative pins must have the same width, but \(pins io A0\) "
                    r"and \(pins io B0 B1\) do not$")):
            dp = DiffPairs("A0", "B0 B1")

    def test_wrong_assert_width(self):
        with self.assertRaisesRegex(AssertionError,
                r"^3 names are specified \(0 1 2\), but 4 names are expected$"):
            DiffPairs("0 1 2", "3 4 5", assert_width=4)


class AttrsTestCase(FHDLTestCase):
    def test_basic(self):
        a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1)
        self.assertEqual(a["IO_STANDARD"], "LVCMOS33")
        self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)")

    def test_remove(self):
        a = Attrs(FOO=None)
        self.assertEqual(a["FOO"], None)
        self.assertEqual(repr(a), "(attrs !FOO)")

    def test_callable(self):
        fn = lambda self: "FOO"
        a = Attrs(FOO=fn)
        self.assertEqual(a["FOO"], fn)
        self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn))

    def test_wrong_value(self):
        with self.assertRaisesRegex(TypeError,
                r"^Value of attribute FOO must be None, int, str, or callable, not 1\.0$"):
            a = Attrs(FOO=1.0)


class ClockTestCase(FHDLTestCase):
    def test_basic(self):
        c = Clock(1_000_000)
        self.assertEqual(c.frequency, 1e6)
        self.assertEqual(c.period, 1e-6)
        self.assertEqual(repr(c), "(clock 1000000.0)")


class SubsignalTestCase(FHDLTestCase):
    def test_basic_pins(self):
        s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33"))
        self.assertEqual(repr(s),
            "(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))")

    def test_basic_diffpairs(self):
        s = Subsignal("a", DiffPairs("A0", "B0"))
        self.assertEqual(repr(s),
            "(subsignal a (diffpairs io (p A0) (n B0)))")

    def test_basic_subsignals(self):
        s = Subsignal("a",
                Subsignal("b", Pins("A0")),
                Subsignal("c", Pins("A1")))
        self.assertEqual(repr(s),
            "(subsignal a (subsignal b (pins io A0)) "
                         "(subsignal c (pins io A1)))")

    def test_attrs(self):
        s = Subsignal("a",
                Subsignal("b", Pins("A0")),
                Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")),
                Attrs(IOSTANDARD="LVCMOS33"))
        self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"})
        self.assertEqual(s.ios[0].attrs, {})
        self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"})

    def test_attrs_many(self):
        s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1"))
        self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"})

    def test_clock(self):
        s = Subsignal("a", Pins("A0"), Clock(1e6))
        self.assertEqual(s.clock.frequency, 1e6)

    def test_wrong_empty_io(self):
        with self.assertRaisesRegex(ValueError, r"^Missing I\/O constraints$"):
            s = Subsignal("a")

    def test_wrong_io(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, or Clock, "
                    r"not 'wrong'$")):
            s = Subsignal("a", "wrong")

    def test_wrong_pins(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
                    r"constraints, but \(pins io A1\) appears after \(pins io A0\)$")):
            s = Subsignal("a", Pins("A0"), Pins("A1"))

    def test_wrong_diffpairs(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
                    r"constraints, but \(pins io A1\) appears after \(diffpairs io \(p A0\) \(n B0\)\)$")):
            s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1"))

    def test_wrong_subsignals(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Pins and DiffPairs are incompatible with other location or subsignal "
                    r"constraints, but \(pins io B0\) appears after \(subsignal b \(pins io A0\)\)$")):
            s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0"))

    def test_wrong_clock(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Clock constraint can only be applied to Pins or DiffPairs, not "
                    r"\(subsignal b \(pins io A0\)\)$")):
            s = Subsignal("a", Subsignal("b", Pins("A0")), Clock(1e6))

    def test_wrong_clock_many(self):
        with self.assertRaisesRegex(ValueError,
                r"^Clock constraint can be applied only once$"):
            s = Subsignal("a", Pins("A0"), Clock(1e6), Clock(1e7))


class ResourceTestCase(FHDLTestCase):
    def test_basic(self):
        r = Resource("serial", 0,
                Subsignal("tx", Pins("A0", dir="o")),
                Subsignal("rx", Pins("A1", dir="i")),
                Attrs(IOSTANDARD="LVCMOS33"))
        self.assertEqual(repr(r), "(resource serial 0"
                                  " (subsignal tx (pins o A0))"
                                  " (subsignal rx (pins i A1))"
                                  " (attrs IOSTANDARD='LVCMOS33'))")

    def test_number_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Resource number must be an integer, not \(pins o 1\)$"):
            # number omitted by accident
            Resource("led", Pins("1", dir="o"))

    def test_family(self):
        ios = [Subsignal("clk", Pins("A0", dir="o"))]
        r1  = Resource.family(0, default_name="spi", ios=ios)
        r2  = Resource.family("spi_flash", 0, default_name="spi", ios=ios)
        r3  = Resource.family("spi_flash", 0, default_name="spi", ios=ios, name_suffix="4x")
        r4  = Resource.family(0, default_name="spi", ios=ios, name_suffix="2x")
        self.assertEqual(r1.name, "spi")
        self.assertEqual(r1.ios, ios)
        self.assertEqual(r2.name, "spi_flash")
        self.assertEqual(r2.ios, ios)
        self.assertEqual(r3.name, "spi_flash_4x")
        self.assertEqual(r3.ios, ios)
        self.assertEqual(r4.name, "spi_2x")
        self.assertEqual(r4.ios, ios)


class ConnectorTestCase(FHDLTestCase):
    def test_string(self):
        c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -")
        self.assertEqual(c.name, "pmod")
        self.assertEqual(c.number, 0)
        self.assertEqual(c.mapping, OrderedDict([
            ("1", "A0"),
            ("2", "A1"),
            ("3", "A2"),
            ("4", "A3"),
            ("7", "A4"),
            ("8", "A5"),
            ("9", "A6"),
            ("10", "A7"),
        ]))
        self.assertEqual(list(c), [
            ("pmod_0:1", "A0"),
            ("pmod_0:2", "A1"),
            ("pmod_0:3", "A2"),
            ("pmod_0:4", "A3"),
            ("pmod_0:7", "A4"),
            ("pmod_0:8", "A5"),
            ("pmod_0:9", "A6"),
            ("pmod_0:10", "A7"),
        ])
        self.assertEqual(repr(c),
            "(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)")

    def test_dict(self):
        c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"})
        self.assertEqual(c.name, "ext")
        self.assertEqual(c.number, 1)
        self.assertEqual(c.mapping, OrderedDict([
            ("DP0", "A0"),
            ("DP1", "A1"),
        ]))

    def test_conn(self):
        c = Connector("pmod", 0, "0 1 2 3 - - 4 5 6 7 - -", conn=("expansion", 0))
        self.assertEqual(c.mapping, OrderedDict([
            ("1", "expansion_0:0"),
            ("2", "expansion_0:1"),
            ("3", "expansion_0:2"),
            ("4", "expansion_0:3"),
            ("7", "expansion_0:4"),
            ("8", "expansion_0:5"),
            ("9", "expansion_0:6"),
            ("10", "expansion_0:7"),
        ]))

    def test_str_name(self):
        c = Connector("ext", "A", "0 1 2")
        self.assertEqual(c.name, "ext")
        self.assertEqual(c.number, "A")

    def test_conn_wrong_name(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Connector must be None or a pair of string \(connector name\) and "
                    r"integer\/string \(connector number\), not \('foo', None\)$")):
            Connector("ext", "A", "0 1 2", conn=("foo", None))

    def test_wrong_io(self):
        with self.assertRaisesRegex(TypeError,
                r"^Connector I\/Os must be a dictionary or a string, not \[\]$"):
            Connector("pmod", 0, [])

    def test_wrong_dict_key_value(self):
        with self.assertRaisesRegex(TypeError,
                r"^Connector pin name must be a string, not 0$"):
            Connector("pmod", 0, {0: "A"})
        with self.assertRaisesRegex(TypeError,
                r"^Platform pin name must be a string, not 0$"):
            Connector("pmod", 0, {"A": 0})


# tests/test_build_plat.py

from amaranth import *
from amaranth.build.plat import *

from .utils import *


class MockPlatform(Platform):
    resources  = []
    connectors = []

    required_tools = []

    def toolchain_prepare(self, fragment, name, **kwargs):
        raise NotImplementedError


class PlatformTestCase(FHDLTestCase):
    def setUp(self):
        self.platform = MockPlatform()

    def test_add_file_str(self):
        self.platform.add_file("x.txt", "foo")
        self.assertEqual(self.platform.extra_files["x.txt"], "foo")

    def test_add_file_bytes(self):
        self.platform.add_file("x.txt", b"foo")
        self.assertEqual(self.platform.extra_files["x.txt"], b"foo")

    def test_add_file_exact_duplicate(self):
        self.platform.add_file("x.txt", b"foo")
        self.platform.add_file("x.txt", b"foo")

    def test_add_file_io(self):
        with open(__file__) as f:
            self.platform.add_file("x.txt", f)
        with open(__file__) as f:
            self.assertEqual(self.platform.extra_files["x.txt"], f.read())

    def test_add_file_wrong_filename(self):
        with self.assertRaisesRegex(TypeError,
                r"^File name must be a string, not 1$"):
            self.platform.add_file(1, "")

    def test_add_file_wrong_contents(self):
        with self.assertRaisesRegex(TypeError,
                r"^File contents must be str, bytes, or a file-like object, not 1$"):
            self.platform.add_file("foo", 1)

    def test_add_file_wrong_duplicate(self):
        self.platform.add_file("foo", "")
        with self.assertRaisesRegex(ValueError,
                r"^File 'foo' already exists$"):
            self.platform.add_file("foo", "bar")

    def test_iter_files(self):
        self.platform.add_file("foo.v", "")
        self.platform.add_file("bar.v", "")
        self.platform.add_file("baz.vhd", "")
        self.assertEqual(list(self.platform.iter_files(".v")),
                         ["foo.v", "bar.v"])
        self.assertEqual(list(self.platform.iter_files(".vhd")),
                         ["baz.vhd"])
        self.assertEqual(list(self.platform.iter_files(".v", ".vhd")),
                         ["foo.v", "bar.v", "baz.vhd"])


# tests/test_build_res.py

# amaranth: UnusedElaboratable=no

from amaranth import *
from amaranth.hdl.rec import *
from amaranth.lib.io import *
from amaranth.build.dsl import *
from amaranth.build.res import *

from .utils import *


class ResourceManagerTestCase(FHDLTestCase):
    def setUp(self):
        self.resources = [
            Resource("clk100", 0, DiffPairs("H1", "H2", dir="i"), Clock(100e6)),
            Resource("clk50", 0, Pins("K1"), Clock(50e6)),
            Resource("user_led", 0, Pins("A0", dir="o")),
            Resource("i2c", 0,
                Subsignal("scl", Pins("N10", dir="o")),
                Subsignal("sda", Pins("N11"))
            )
        ]
        self.connectors = [
            Connector("pmod", 0, "B0 B1 B2 B3 - -"),
        ]
        self.cm = ResourceManager(self.resources, self.connectors)

    def test_basic(self):
        self.cm = ResourceManager(self.resources, self.connectors)
        self.assertEqual(self.cm.resources, {
            ("clk100",   0): self.resources[0],
            ("clk50",    0): self.resources[1],
            ("user_led", 0): self.resources[2],
            ("i2c",      0): self.resources[3]
        })
        self.assertEqual(self.cm.connectors, {
            ("pmod", 0): self.connectors[0],
        })

    def test_add_resources(self):
        new_resources = [
            Resource("user_led", 1, Pins("A1", dir="o"))
        ]
        self.cm.add_resources(new_resources)
        self.assertEqual(self.cm.resources, {
            ("clk100",   0): self.resources[0],
            ("clk50",    0): self.resources[1],
            ("user_led", 0): self.resources[2],
            ("i2c",      0): self.resources[3],
            ("user_led", 1): new_resources[0]
        })

    def test_lookup(self):
        r = self.cm.lookup("user_led", 0)
        self.assertIs(r, self.cm.resources["user_led", 0])

    def test_request_basic(self):
        r = self.cm.lookup("user_led", 0)
        user_led = self.cm.request("user_led", 0)

        self.assertIsInstance(user_led, Pin)
        self.assertEqual(user_led.name, "user_led_0")
        self.assertEqual(user_led.width, 1)
        self.assertEqual(user_led.dir, "o")

        ports = list(self.cm.iter_ports())
        self.assertEqual(len(ports), 1)

        self.assertEqual(list(self.cm.iter_port_constraints()), [
            ("user_led_0__io", ["A0"], {})
        ])

    def test_request_with_dir(self):
        i2c = self.cm.request("i2c", 0, dir={"sda": "o"})
        self.assertIsInstance(i2c, Record)
        self.assertIsInstance(i2c.sda, Pin)
        self.assertEqual(i2c.sda.dir, "o")

    def test_request_tristate(self):
        i2c = self.cm.request("i2c", 0)
        self.assertEqual(i2c.sda.dir, "io")

        ports = list(self.cm.iter_ports())
        self.assertEqual(len(ports), 2)
        scl, sda = ports
        self.assertEqual(ports[1].name, "i2c_0__sda__io")
        self.assertEqual(ports[1].width, 1)

        scl_info, sda_info = self.cm.iter_single_ended_pins()
        self.assertIs(scl_info[0], i2c.scl)
        self.assertIs(scl_info[1].io, scl)
        self.assertEqual(scl_info[2], {})
        self.assertEqual(scl_info[3], False)
        self.assertIs(sda_info[0], i2c.sda)
        self.assertIs(sda_info[1].io, sda)

        self.assertEqual(list(self.cm.iter_port_constraints()), [
            ("i2c_0__scl__io", ["N10"], {}),
            ("i2c_0__sda__io", ["N11"], {})
        ])

    def test_request_diffpairs(self):
        clk100 = self.cm.request("clk100", 0)
        self.assertIsInstance(clk100, Pin)
        self.assertEqual(clk100.dir, "i")
        self.assertEqual(clk100.width, 1)

        ports = list(self.cm.iter_ports())
        self.assertEqual(len(ports), 2)
        p, n = ports
        self.assertEqual(p.name, "clk100_0__p")
        self.assertEqual(p.width, clk100.width)
        self.assertEqual(n.name, "clk100_0__n")
        self.assertEqual(n.width, clk100.width)

        clk100_info, = self.cm.iter_differential_pins()
        self.assertIs(clk100_info[0], clk100)
        self.assertIs(clk100_info[1].p, p)
        self.assertIs(clk100_info[1].n, n)
        self.assertEqual(clk100_info[2], {})
        self.assertEqual(clk100_info[3], False)

        self.assertEqual(list(self.cm.iter_port_constraints()), [
            ("clk100_0__p", ["H1"], {}),
            ("clk100_0__n", ["H2"], {}),
        ])

    def test_request_inverted(self):
        new_resources = [
            Resource("cs", 0, PinsN("X0")),
            Resource("clk", 0, DiffPairsN("Y0", "Y1")),
        ]
        self.cm.add_resources(new_resources)

        cs = self.cm.request("cs")
        clk = self.cm.request("clk")
        cs_io, clk_p, clk_n = self.cm.iter_ports()

        cs_info, = self.cm.iter_single_ended_pins()
        self.assertIs(cs_info[0], cs)
        self.assertIs(cs_info[1].io, cs_io)
        self.assertEqual(cs_info[2], {})
        self.assertEqual(cs_info[3], True)

        clk_info, = self.cm.iter_differential_pins()
        self.assertIs(clk_info[0], clk)
        self.assertIs(clk_info[1].p, clk_p)
        self.assertIs(clk_info[1].n, clk_n)
        self.assertEqual(clk_info[2], {})
        self.assertEqual(clk_info[3], True)

    def test_request_raw(self):
        clk50 = self.cm.request("clk50", 0, dir="-")
        self.assertIsInstance(clk50, Record)
        self.assertIsInstance(clk50.io, Signal)

        ports = list(self.cm.iter_ports())
        self.assertEqual(len(ports), 1)
        self.assertIs(ports[0], clk50.io)

    def test_request_raw_diffpairs(self):
        clk100 = self.cm.request("clk100", 0, dir="-")
        self.assertIsInstance(clk100, Record)
        self.assertIsInstance(clk100.p, Signal)
        self.assertIsInstance(clk100.n, Signal)

        ports = list(self.cm.iter_ports())
        self.assertEqual(len(ports), 2)
        self.assertIs(ports[0], clk100.p)
        self.assertIs(ports[1], clk100.n)

    def test_request_via_connector(self):
        self.cm.add_resources([
            Resource("spi", 0,
                Subsignal("ss",   Pins("1", conn=("pmod", 0))),
                Subsignal("clk",  Pins("2", conn=("pmod", 0))),
                Subsignal("miso", Pins("3", conn=("pmod", 0))),
                Subsignal("mosi", Pins("4", conn=("pmod", 0))),
            )
        ])
        spi0 = self.cm.request("spi", 0)
        self.assertEqual(list(self.cm.iter_port_constraints()), [
            ("spi_0__ss__io",   ["B0"], {}),
            ("spi_0__clk__io",  ["B1"], {}),
            ("spi_0__miso__io", ["B2"], {}),
            ("spi_0__mosi__io", ["B3"], {}),
        ])

    def test_request_via_nested_connector(self):
        new_connectors = [
            Connector("pmod_extension", 0, "1 2 3 4 - -", conn=("pmod", 0)),
        ]
        self.cm.add_connectors(new_connectors)
        self.cm.add_resources([
            Resource("spi", 0,
                Subsignal("ss",   Pins("1", conn=("pmod_extension", 0))),
                Subsignal("clk",  Pins("2", conn=("pmod_extension", 0))),
                Subsignal("miso", Pins("3", conn=("pmod_extension", 0))),
                Subsignal("mosi", Pins("4", conn=("pmod_extension", 0))),
            )
        ])
        spi0 = self.cm.request("spi", 0)
        self.assertEqual(list(self.cm.iter_port_constraints()), [
            ("spi_0__ss__io",   ["B0"], {}),
            ("spi_0__clk__io",  ["B1"], {}),
            ("spi_0__miso__io", ["B2"], {}),
            ("spi_0__mosi__io", ["B3"], {}),
        ])

    def test_request_clock(self):
        clk100 = self.cm.request("clk100", 0)
        clk50 = self.cm.request("clk50", 0, dir="i")
        clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports()
        self.assertEqual(list(self.cm.iter_clock_constraints()), [
            (clk100.i, clk100_port_p, 100e6),
            (clk50.i, clk50_port, 50e6)
        ])

    def test_add_clock(self):
        i2c = self.cm.request("i2c")
        self.cm.add_clock_constraint(i2c.scl.o, 100e3)
        self.assertEqual(list(self.cm.iter_clock_constraints()), [
            (i2c.scl.o, None, 100e3)
        ])

    def test_wrong_resources(self):
        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Resource$"):
            self.cm.add_resources(['wrong'])

    def test_wrong_resources_duplicate(self):
        with self.assertRaisesRegex(NameError,
                (r"^Trying to add \(resource user_led 0 \(pins o A1\)\), but "
                    r"\(resource user_led 0 \(pins o A0\)\) has the same name and number$")):
            self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))])

    def test_wrong_connectors(self):
        with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Connector$"):
            self.cm.add_connectors(['wrong'])

    def test_wrong_connectors_duplicate(self):
        with self.assertRaisesRegex(NameError,
                (r"^Trying to add \(connector pmod 0 1=>1 2=>2\), but "
                    r"\(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3\) has the same name and number$")):
            self.cm.add_connectors([Connector("pmod", 0, "1 2")])

    def test_wrong_lookup(self):
        with self.assertRaisesRegex(ResourceError,
                r"^Resource user_led#1 does not exist$"):
            r = self.cm.lookup("user_led", 1)

    def test_wrong_clock_signal(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object None is not a Signal$"):
            self.cm.add_clock_constraint(None, 10e6)

    def test_wrong_clock_frequency(self):
        with self.assertRaisesRegex(TypeError,
                r"^Frequency must be a number, not None$"):
            self.cm.add_clock_constraint(Signal(), None)

    def test_wrong_request_duplicate(self):
        with self.assertRaisesRegex(ResourceError,
                r"^Resource user_led#0 has already been requested$"):
            self.cm.request("user_led", 0)
            self.cm.request("user_led", 0)

    def test_wrong_request_duplicate_physical(self):
        self.cm.add_resources([
            Resource("clk20", 0, Pins("H1", dir="i")),
        ])
        self.cm.request("clk100", 0)
        with self.assertRaisesRegex(ResourceError,
                (r"^Resource component clk20_0 uses physical pin H1, but it is already "
                    r"used by resource component clk100_0 that was requested earlier$")):
            self.cm.request("clk20", 0)

    def test_wrong_request_with_dir(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", "
                    r"not 'wrong'$")):
            user_led = self.cm.request("user_led", 0, dir="wrong")

    def test_wrong_request_with_dir_io(self):
        with self.assertRaisesRegex(ValueError,
                (r"^Direction of \(pins o A0\) cannot be changed from \"o\" to \"i\"; direction "
                    r"can be changed from \"io\" to \"i\", \"o\", or \"oe\", or from anything "
                    r"to \"-\"$")):
            user_led = self.cm.request("user_led", 0, dir="i")

    def test_wrong_request_with_dir_dict(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Directions must be a dict, not 'i', because \(resource i2c 0 \(subsignal scl "
                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
                    r"has subsignals$")):
            i2c = self.cm.request("i2c", 0, dir="i")

    def test_wrong_request_with_wrong_xdr(self):
        with self.assertRaisesRegex(ValueError,
                r"^Data rate of \(pins o A0\) must be a non-negative integer, not -1$"):
            user_led = self.cm.request("user_led", 0, xdr=-1)

    def test_wrong_request_with_xdr_dict(self):
        with self.assertRaisesRegex(TypeError,
                r"^Data rate must be a dict, not 2, because \(resource i2c 0 \(subsignal scl "
                    r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) "
                    r"has subsignals$"):
            i2c = self.cm.request("i2c", 0, xdr=2)

    def test_wrong_clock_constraint_twice(self):
        clk100 = self.cm.request("clk100")
        with self.assertRaisesRegex(ValueError,
                (r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already "
                    r"constrained to 100000000\.0 Hz$")):
            self.cm.add_clock_constraint(clk100.i, 1e6)


# tests/test_compat.py

from amaranth.hdl.ir import Fragment
from amaranth.compat import *

from .utils import *


class CompatTestCase(FHDLTestCase):
    def test_fragment_get(self):
        m = Module()
        f = Fragment.get(m, platform=None)


# tests/test_examples.py

import sys
import subprocess
from pathlib import Path

from .utils import *


def example_test(name):
    path = (Path(__file__).parent / ".." / "examples" / name).resolve()
    def test_function(self):
        subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"],
                              stdout=subprocess.DEVNULL)
    return test_function


class ExamplesTestCase(FHDLTestCase):
    test_alu        = example_test("basic/alu.py")
    test_alu_hier   = example_test("basic/alu_hier.py")
    test_arst       = example_test("basic/arst.py")
    test_cdc        = example_test("basic/cdc.py")
    test_ctr        = example_test("basic/ctr.py")
    test_ctr_en     = example_test("basic/ctr_en.py")
    test_fsm        = example_test("basic/fsm.py")
    test_gpio       = example_test("basic/gpio.py")
    test_inst       = example_test("basic/inst.py")
    test_mem        = example_test("basic/mem.py")
    test_pmux       = example_test("basic/pmux.py")
    test_por        = example_test("basic/por.py")

    def test_uart(self):
        path = (Path(__file__).parent / ".." / "examples" / "basic" / "uart.py").resolve()
        subprocess.check_call([sys.executable, str(path), "generate"],
                              stdout=subprocess.DEVNULL)


# tests/test_hdl_ast.py

import warnings
from enum import Enum

from amaranth.hdl.ast import *
from amaranth.lib.enum import Enum as AmaranthEnum

from .utils import *
from amaranth._utils import _ignore_deprecated


class UnsignedEnum(Enum):
    FOO = 1
    BAR = 2
    BAZ = 3


class SignedEnum(Enum):
    FOO = -1
    BAR =  0
    BAZ = +1


class StringEnum(Enum):
    FOO = "a"
    BAR = "b"


class TypedEnum(int, Enum):
    FOO = 1
    BAR = 2
    BAZ = 3


class ShapeTestCase(FHDLTestCase):
    def test_make(self):
        s1 = Shape()
        self.assertEqual(s1.width, 1)
        self.assertEqual(s1.signed, False)
        s2 = Shape(signed=True)
        self.assertEqual(s2.width, 1)
        self.assertEqual(s2.signed, True)
        s3 = Shape(3, True)
        self.assertEqual(s3.width, 3)
        self.assertEqual(s3.signed, True)

    def test_make_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Width must be a non-negative integer, not -1$"):
            Shape(-1)

    def test_compare_non_shape(self):
        self.assertNotEqual(Shape(1, True), "hi")

    def test_repr(self):
        self.assertEqual(repr(Shape()), "unsigned(1)")
        self.assertEqual(repr(Shape(2, True)), "signed(2)")

    def test_convert_tuple_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^cannot unpack non-iterable Shape object$"):
            width, signed = Shape()

    def test_unsigned(self):
        s1 = unsigned(2)
        self.assertIsInstance(s1, Shape)
        self.assertEqual(s1.width, 2)
        self.assertEqual(s1.signed, False)

    def test_signed(self):
        s1 = signed(2)
        self.assertIsInstance(s1, Shape)
        self.assertEqual(s1.width, 2)
        self.assertEqual(s1.signed, True)

    def test_cast_shape(self):
        s1 = Shape.cast(unsigned(1))
        self.assertEqual(s1.width, 1)
        self.assertEqual(s1.signed, False)
        s2 = Shape.cast(signed(3))
        self.assertEqual(s2.width, 3)
        self.assertEqual(s2.signed, True)

    def test_cast_int(self):
        s1 = Shape.cast(2)
        self.assertEqual(s1.width, 2)
        self.assertEqual(s1.signed, False)

    def test_cast_int_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Width must be a non-negative integer, not -1$"):
            Shape.cast(-1)

    def test_cast_tuple_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object \(1, True\) cannot be converted to an Amaranth shape$"):
            Shape.cast((1, True))

    def test_cast_range(self):
        s1 = Shape.cast(range(0, 8))
        self.assertEqual(s1.width, 3)
        self.assertEqual(s1.signed, False)
        s2 = Shape.cast(range(0, 9))
        self.assertEqual(s2.width, 4)
        self.assertEqual(s2.signed, False)
        s3 = Shape.cast(range(-7, 8))
        self.assertEqual(s3.width, 4)
        self.assertEqual(s3.signed, True)
        s4 = Shape.cast(range(0, 1))
        self.assertEqual(s4.width, 1)
        self.assertEqual(s4.signed, False)
        s5 = Shape.cast(range(-1, 0))
        self.assertEqual(s5.width, 1)
        self.assertEqual(s5.signed, True)
        s6 = Shape.cast(range(0, 0))
        self.assertEqual(s6.width, 0)
        self.assertEqual(s6.signed, False)
        s7 = Shape.cast(range(-1, -1))
        self.assertEqual(s7.width, 0)
        self.assertEqual(s7.signed, True)
        s8 = Shape.cast(range(0, 10, 3))
        self.assertEqual(s8.width, 4)
        self.assertEqual(s8.signed, False)

    def test_cast_enum(self):
        s1 = Shape.cast(UnsignedEnum)
        self.assertEqual(s1.width, 2)
        self.assertEqual(s1.signed, False)
        s2 = Shape.cast(SignedEnum)
        self.assertEqual(s2.width, 2)
        self.assertEqual(s2.signed, True)

    def test_cast_enum_bad(self):
        with self.assertRaisesRegex(TypeError,
                r"^Only enumerations whose members have constant-castable values can be used "
                r"in Amaranth code$"):
            Shape.cast(StringEnum)

    def test_cast_bad(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object 'foo' cannot be converted to an Amaranth shape$"):
            Shape.cast("foo")


class MockShapeCastable(ShapeCastable):
    def __init__(self, dest):
        self.dest = dest

    def as_shape(self):
        return self.dest

    def __call__(self, value):
        return value

    def const(self, init):
        return Const(init, self.dest)


class ShapeCastableTestCase(FHDLTestCase):
    def test_no_override(self):
        with self.assertRaisesRegex(TypeError,
                r"^Class 'MockShapeCastableNoOverride' deriving from `ShapeCastable` must "
                r"override the `as_shape` method$"):
            class MockShapeCastableNoOverride(ShapeCastable):
                def __init__(self):
                    pass

    def test_cast(self):
        sc = MockShapeCastable(unsigned(2))
        self.assertEqual(Shape.cast(sc), unsigned(2))

    def test_recurse_bad(self):
        sc = MockShapeCastable(None)
        sc.dest = sc
        with self.assertRaisesRegex(RecursionError,
                r"^Shape-castable object <.+> casts to itself$"):
            Shape.cast(sc)

    def test_recurse(self):
        sc = MockShapeCastable(MockShapeCastable(unsigned(1)))
        self.assertEqual(Shape.cast(sc), unsigned(1))


class ValueTestCase(FHDLTestCase):
    def test_cast(self):
        self.assertIsInstance(Value.cast(0), Const)
        self.assertIsInstance(Value.cast(True), Const)
        c = Const(0)
        self.assertIs(Value.cast(c), c)
        with self.assertRaisesRegex(TypeError,
                r"^Object 'str' cannot be converted to an Amaranth value$"):
            Value.cast("str")

    def test_cast_enum(self):
        e1 = Value.cast(UnsignedEnum.FOO)
        self.assertIsInstance(e1, Const)
        self.assertEqual(e1.shape(), unsigned(2))
        e2 = Value.cast(SignedEnum.FOO)
        self.assertIsInstance(e2, Const)
        self.assertEqual(e2.shape(), signed(2))

    def test_cast_typedenum(self):
        e1 = Value.cast(TypedEnum.FOO)
        self.assertIsInstance(e1, Const)
        self.assertEqual(e1.shape(), unsigned(2))

    def test_cast_enum_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Only enumerations whose members have constant-castable values can be used "
                r"in Amaranth code$"):
            Value.cast(StringEnum.FOO)

    def test_bool(self):
        with self.assertRaisesRegex(TypeError,
                r"^Attempted to convert Amaranth value to Python boolean$"):
            if Const(0):
                pass

    def test_len(self):
        self.assertEqual(len(Const(10)), 4)

    def test_getitem_int(self):
        s1 = Const(10)[0]
        self.assertIsInstance(s1, Slice)
        self.assertEqual(s1.start, 0)
        self.assertEqual(s1.stop, 1)
        s2 = Const(10)[-1]
        self.assertIsInstance(s2, Slice)
        self.assertEqual(s2.start, 3)
        self.assertEqual(s2.stop, 4)
        with self.assertRaisesRegex(IndexError,
                r"^Index 5 is out of bounds for a 4-bit value$"):
            Const(10)[5]

    def test_getitem_slice(self):
        s1 = Const(10)[1:3]
        self.assertIsInstance(s1, Slice)
        self.assertEqual(s1.start, 1)
        self.assertEqual(s1.stop, 3)
        s2 = Const(10)[1:-2]
        self.assertIsInstance(s2, Slice)
        self.assertEqual(s2.start, 1)
        self.assertEqual(s2.stop, 2)
        s3 = Const(31)[::2]
        self.assertIsInstance(s3, Cat)
        self.assertIsInstance(s3.parts[0], Slice)
        self.assertEqual(s3.parts[0].start, 0)
        self.assertEqual(s3.parts[0].stop, 1)
        self.assertIsInstance(s3.parts[1], Slice)
        self.assertEqual(s3.parts[1].start, 2)
        self.assertEqual(s3.parts[1].stop, 3)
        self.assertIsInstance(s3.parts[2], Slice)
        self.assertEqual(s3.parts[2].start, 4)
        self.assertEqual(s3.parts[2].stop, 5)

    def test_getitem_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Cannot index value with 'str'$"):
            Const(31)["str"]

    def test_shift_left(self):
        self.assertRepr(Const(256, unsigned(9)).shift_left(0),
                        "(cat (const 0'd0) (const 9'd256))")

        self.assertRepr(Const(256, unsigned(9)).shift_left(1),
                        "(cat (const 1'd0) (const 9'd256))")
        self.assertRepr(Const(256, unsigned(9)).shift_left(5),
                        "(cat (const 5'd0) (const 9'd256))")
        self.assertRepr(Const(256, signed(9)).shift_left(1),
                        "(s (cat (const 1'd0) (const 9'sd-256)))")
        self.assertRepr(Const(256, signed(9)).shift_left(5),
                        "(s (cat (const 5'd0) (const 9'sd-256)))")

        self.assertRepr(Const(256, unsigned(9)).shift_left(-1),
                        "(slice (const 9'd256) 1:9)")
        self.assertRepr(Const(256, unsigned(9)).shift_left(-5),
                        "(slice (const 9'd256) 5:9)")
        self.assertRepr(Const(256, signed(9)).shift_left(-1),
                        "(s (slice (const 9'sd-256) 1:9))")
        self.assertRepr(Const(256, signed(9)).shift_left(-5),
                        "(s (slice (const 9'sd-256) 5:9))")
        self.assertRepr(Const(256, signed(9)).shift_left(-15),
                        "(s (slice (const 9'sd-256) 9:9))")

    def test_shift_left_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be an integer, not 'str'$"):
            Const(31).shift_left("str")

    def test_shift_right(self):
        self.assertRepr(Const(256, unsigned(9)).shift_right(0),
                        "(slice (const 9'd256) 0:9)")

        self.assertRepr(Const(256, unsigned(9)).shift_right(-1),
                        "(cat (const 1'd0) (const 9'd256))")
        self.assertRepr(Const(256, unsigned(9)).shift_right(-5),
                        "(cat (const 5'd0) (const 9'd256))")
        self.assertRepr(Const(256, signed(9)).shift_right(-1),
                        "(s (cat (const 1'd0) (const 9'sd-256)))")
        self.assertRepr(Const(256, signed(9)).shift_right(-5),
                        "(s (cat (const 5'd0) (const 9'sd-256)))")

        self.assertRepr(Const(256, unsigned(9)).shift_right(1),
                        "(slice (const 9'd256) 1:9)")
        self.assertRepr(Const(256, unsigned(9)).shift_right(5),
                        "(slice (const 9'd256) 5:9)")
        self.assertRepr(Const(256, signed(9)).shift_right(1),
                        "(s (slice (const 9'sd-256) 1:9))")
        self.assertRepr(Const(256, signed(9)).shift_right(5),
                        "(s (slice (const 9'sd-256) 5:9))")
        self.assertRepr(Const(256, signed(9)).shift_right(15),
                        "(s (slice (const 9'sd-256) 9:9))")

    def test_shift_right_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be an integer, not 'str'$"):
            Const(31).shift_left("str")

    def test_rotate_left(self):
        self.assertRepr(Const(256).rotate_left(1),
                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
        self.assertRepr(Const(256).rotate_left(7),
                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
        self.assertRepr(Const(256).rotate_left(-1),
                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
        self.assertRepr(Const(256).rotate_left(-7),
                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
        self.assertRepr(Const(0, 0).rotate_left(3),
                        "(cat (slice (const 0'd0) 0:0) (slice (const 0'd0) 0:0))")
        self.assertRepr(Const(0, 0).rotate_left(-3),
                        "(cat (slice (const 0'd0) 0:0) (slice (const 0'd0) 0:0))")

    def test_rotate_left_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Rotate amount must be an integer, not 'str'$"):
            Const(31).rotate_left("str")

    def test_rotate_right(self):
        self.assertRepr(Const(256).rotate_right(1),
                        "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))")
        self.assertRepr(Const(256).rotate_right(7),
                        "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))")
        self.assertRepr(Const(256).rotate_right(-1),
                        "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))")
        self.assertRepr(Const(256).rotate_right(-7),
                        "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))")
        self.assertRepr(Const(0, 0).rotate_right(3),
                        "(cat (slice (const 0'd0) 0:0) (slice (const 0'd0) 0:0))")
        self.assertRepr(Const(0, 0).rotate_right(-3),
                        "(cat (slice (const 0'd0) 0:0) (slice (const 0'd0) 0:0))")

    def test_rotate_right_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Rotate amount must be an integer, not 'str'$"):
            Const(31).rotate_right("str")

    def test_replicate_shape(self):
        s1 = Const(10).replicate(3)
        self.assertEqual(s1.shape(), unsigned(12))
        self.assertIsInstance(s1.shape(), Shape)
        s2 = Const(10).replicate(0)
        self.assertEqual(s2.shape(), unsigned(0))

    def test_replicate_count_wrong(self):
        with self.assertRaises(TypeError):
            Const(10).replicate(-1)
        with self.assertRaises(TypeError):
            Const(10).replicate("str")

    def test_replicate_repr(self):
        s = Const(10).replicate(3)
        self.assertEqual(repr(s), "(cat (const 4'd10) (const 4'd10) (const 4'd10))")


class ConstTestCase(FHDLTestCase):
    def test_shape(self):
        self.assertEqual(Const(0).shape(),   unsigned(1))
        self.assertIsInstance(Const(0).shape(), Shape)
        self.assertEqual(Const(1).shape(),   unsigned(1))
        self.assertEqual(Const(10).shape(),  unsigned(4))
        self.assertEqual(Const(-10).shape(), signed(5))

        self.assertEqual(Const(1, 4).shape(),          unsigned(4))
        self.assertEqual(Const(-1, 4).shape(),         signed(4))
        self.assertEqual(Const(1, signed(4)).shape(),  signed(4))
        self.assertEqual(Const(0, unsigned(0)).shape(), unsigned(0))

    def test_shape_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Width must be a non-negative integer, not -1$"):
            Const(1, -1)

    def test_wrong_fencepost(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value 10 equals the non-inclusive end of the constant shape "
                r"range\(0, 10\); this is likely an off-by-one error$"):
            Const(10, range(10))

    def test_normalization(self):
        self.assertEqual(Const(0b10110, signed(5)).value, -10)
        self.assertEqual(Const(0b10000, signed(4)).value, 0)
        self.assertEqual(Const(-16, 4).value, 0)

    def test_value(self):
        self.assertEqual(Const(10).value, 10)

    def test_repr(self):
        self.assertEqual(repr(Const(10)),  "(const 4'd10)")
        self.assertEqual(repr(Const(-10)), "(const 5'sd-10)")

    def test_hash(self):
        with self.assertRaises(TypeError):
            hash(Const(0))


class OperatorTestCase(FHDLTestCase):
    def test_bool(self):
        v = Const(0, 4).bool()
        self.assertEqual(repr(v), "(b (const 4'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_invert(self):
        v = ~Const(0, 4)
        self.assertEqual(repr(v), "(~ (const 4'd0))")
        self.assertEqual(v.shape(), unsigned(4))

    def test_as_unsigned(self):
        v = Const(-1, signed(4)).as_unsigned()
        self.assertEqual(repr(v), "(u (const 4'sd-1))")
        self.assertEqual(v.shape(), unsigned(4))

    def test_as_signed(self):
        v = Const(1, unsigned(4)).as_signed()
        self.assertEqual(repr(v), "(s (const 4'd1))")
        self.assertEqual(v.shape(), signed(4))

    def test_pos(self):
        self.assertRepr(+Const(10), "(const 4'd10)")

    def test_neg(self):
        v1 = -Const(0, unsigned(4))
        self.assertEqual(repr(v1), "(- (const 4'd0))")
        self.assertEqual(v1.shape(), signed(5))
        v2 = -Const(0, signed(4))
        self.assertEqual(repr(v2), "(- (const 4'sd0))")
        self.assertEqual(v2.shape(), signed(5))

    def test_add(self):
        v1 = Const(0, unsigned(4)) + Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(+ (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(7))
        v2 = Const(0, signed(4)) + Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(7))
        v3 = Const(0, signed(4)) + Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(6))
        v4 = Const(0, unsigned(4)) + Const(0, signed(4))
        self.assertEqual(v4.shape(), signed(6))
        v5 = 10 + Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(5))

    def test_sub(self):
        v1 = Const(0, unsigned(4)) - Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(- (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), signed(7))
        v2 = Const(0, signed(4)) - Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(7))
        v3 = Const(0, signed(4)) - Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(6))
        v4 = Const(0, unsigned(4)) - Const(0, signed(4))
        self.assertEqual(v4.shape(), signed(6))
        v5 = 10 - Const(0, 4)
        self.assertEqual(v5.shape(), signed(5))
        v6 = 1 - Const(2)
        self.assertEqual(v6.shape(), signed(3))

    def test_mul(self):
        v1 = Const(0, unsigned(4)) * Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(* (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(10))
        v2 = Const(0, signed(4)) * Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(10))
        v3 = Const(0, signed(4)) * Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(8))
        v5 = 10 * Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(8))

    def test_mod(self):
        v1 = Const(0, unsigned(4)) % Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(% (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(6))
        v3 = Const(0, signed(4)) % Const(0, unsigned(4))
        self.assertEqual(v3.shape(), unsigned(4))
        v4 = Const(0, signed(4)) % Const(0, signed(6))
        self.assertEqual(v4.shape(), signed(6))
        v5 = 10 % Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(4))

    def test_floordiv(self):
        v1 = Const(0, unsigned(4)) // Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(// (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(4))
        v3 = Const(0, signed(4)) // Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(4))
        v4 = Const(0, signed(4)) // Const(0, signed(6))
        self.assertEqual(v4.shape(), signed(5))
        v5 = 10 // Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(4))

    def test_and(self):
        v1 = Const(0, unsigned(4)) & Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(& (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(6))
        v2 = Const(0, signed(4)) & Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(6))
        v3 = Const(0, signed(4)) & Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(5))
        v4 = Const(0, unsigned(4)) & Const(0, signed(4))
        self.assertEqual(v4.shape(), signed(5))
        v5 = 10 & Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(4))

    def test_or(self):
        v1 = Const(0, unsigned(4)) | Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(| (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(6))
        v2 = Const(0, signed(4)) | Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(6))
        v3 = Const(0, signed(4)) | Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(5))
        v4 = Const(0, unsigned(4)) | Const(0, signed(4))
        self.assertEqual(v4.shape(), signed(5))
        v5 = 10 | Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(4))

    def test_xor(self):
        v1 = Const(0, unsigned(4)) ^ Const(0, unsigned(6))
        self.assertEqual(repr(v1), "(^ (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(6))
        v2 = Const(0, signed(4)) ^ Const(0, signed(6))
        self.assertEqual(v2.shape(), signed(6))
        v3 = Const(0, signed(4)) ^ Const(0, unsigned(4))
        self.assertEqual(v3.shape(), signed(5))
        v4 = Const(0, unsigned(4)) ^ Const(0, signed(4))
        self.assertEqual(v4.shape(), signed(5))
        v5 = 10 ^ Const(0, 4)
        self.assertEqual(v5.shape(), unsigned(4))

    def test_shl(self):
        v1 = Const(1, 4) << Const(4)
        self.assertEqual(repr(v1), "(<< (const 4'd1) (const 3'd4))")
        self.assertEqual(v1.shape(), unsigned(11))

    def test_shl_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be unsigned$"):
            1 << Const(0, signed(6))
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be unsigned$"):
            Const(1, unsigned(4)) << -1

    def test_shr(self):
        v1 = Const(1, 4) >> Const(4)
        self.assertEqual(repr(v1), "(>> (const 4'd1) (const 3'd4))")
        self.assertEqual(v1.shape(), unsigned(4))

    def test_shr_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be unsigned$"):
            1 << Const(0, signed(6))
        with self.assertRaisesRegex(TypeError,
                r"^Shift amount must be unsigned$"):
            Const(1, unsigned(4)) << -1

    def test_lt(self):
        v = Const(0, 4) < Const(0, 6)
        self.assertEqual(repr(v), "(< (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_le(self):
        v = Const(0, 4) <= Const(0, 6)
        self.assertEqual(repr(v), "(<= (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_gt(self):
        v = Const(0, 4) > Const(0, 6)
        self.assertEqual(repr(v), "(> (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_ge(self):
        v = Const(0, 4) >= Const(0, 6)
        self.assertEqual(repr(v), "(>= (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_eq(self):
        v = Const(0, 4) == Const(0, 6)
        self.assertEqual(repr(v), "(== (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_ne(self):
        v = Const(0, 4) != Const(0, 6)
        self.assertEqual(repr(v), "(!= (const 4'd0) (const 6'd0))")
        self.assertEqual(v.shape(), unsigned(1))

    def test_mux(self):
        s  = Const(0)
        v1 = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
        self.assertEqual(repr(v1), "(m (const 1'd0) (const 4'd0) (const 6'd0))")
        self.assertEqual(v1.shape(), unsigned(6))
        v2 = Mux(s, Const(0, signed(4)), Const(0, signed(6)))
        self.assertEqual(v2.shape(), signed(6))
        v3 = Mux(s, Const(0, signed(4)), Const(0, unsigned(4)))
        self.assertEqual(v3.shape(), signed(5))
        v4 = Mux(s, Const(0, unsigned(4)), Const(0, signed(4)))
        self.assertEqual(v4.shape(), signed(5))

    def test_mux_wide(self):
        s = Const(0b100)
        v = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6)))
        self.assertEqual(repr(v), "(m (const 3'd4) (const 4'd0) (const 6'd0))")

    def test_mux_bool(self):
        v = Mux(True, Const(0), Const(0))
        self.assertEqual(repr(v), "(m (const 1'd1) (const 1'd0) (const 1'd0))")

    def test_any(self):
        v = Const(0b101).any()
        self.assertEqual(repr(v), "(r| (const 3'd5))")

    def test_all(self):
        v = Const(0b101).all()
        self.assertEqual(repr(v), "(r& (const 3'd5))")

    def test_xor_value(self):
        v = Const(0b101).xor()
        self.assertEqual(repr(v), "(r^ (const 3'd5))")

    def test_matches(self):
        s = Signal(4)
        self.assertRepr(s.matches(), "(const 1'd1)")
        self.assertRepr(s.matches(1), """
        (== (sig s) (const 1'd1))
        """)
        self.assertRepr(s.matches(0, 1), """
        (r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1))))
        """)
        self.assertRepr(s.matches("10--"), """
        (== (& (sig s) (const 4'd12)) (const 4'd8))
        """)
        self.assertRepr(s.matches("1 0--"), """
        (== (& (sig s) (const 4'd12)) (const 4'd8))
        """)

    def test_matches_enum(self):
        s = Signal(SignedEnum)
        self.assertRepr(s.matches(SignedEnum.FOO), """
        (== (sig s) (const 2'sd-1))
        """)

    def test_matches_const_castable(self):
        s = Signal(4)
        self.assertRepr(s.matches(Cat(C(0b10, 2), C(0b11, 2))), """
        (== (sig s) (const 4'd14))
        """)

    def test_matches_width_wrong(self):
        s = Signal(4)
        with self.assertRaisesRegex(SyntaxError,
                r"^Match pattern '--' must have the same width as match value \(which is 4\)$"):
            s.matches("--")
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Match pattern '22' \(5'10110\) is wider than match value \(which has "
                r"width 4\); comparison will never be true$"):
            s.matches(0b10110)
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Match pattern '\(cat \(const 1'd0\) \(const 4'd11\)\)' \(5'10110\) is wider "
                r"than match value \(which has width 4\); comparison will never be true$"):
            s.matches(Cat(0, C(0b1011, 4)))

    def test_matches_bits_wrong(self):
        s = Signal(4)
        with self.assertRaisesRegex(SyntaxError,
                r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
                r"and may include whitespace$"):
            s.matches("abc")

    def test_matches_pattern_wrong(self):
        s = Signal(4)
        with self.assertRaisesRegex(SyntaxError,
                r"^Match pattern must be a string or a constant-castable expression, not 1\.0$"):
            s.matches(1.0)

    def test_hash(self):
        with self.assertRaises(TypeError):
            hash(Const(0) + Const(0))

    def test_abs(self):
        s = Signal(4)
        self.assertRepr(abs(s), """
        (sig s)
        """)
        s = Signal(signed(4))
        self.assertRepr(abs(s), """
        (slice (m (>= (sig s) (const 1'd0)) (sig s) (- (sig s))) 0:4)
        """)
        self.assertEqual(abs(s).shape(), unsigned(4))


class SliceTestCase(FHDLTestCase):
    def test_shape(self):
        s1 = Const(10)[2]
        self.assertEqual(s1.shape(), unsigned(1))
        self.assertIsInstance(s1.shape(), Shape)
        s2 = Const(-10)[0:2]
        self.assertEqual(s2.shape(), unsigned(2))

    def test_start_end_negative(self):
        c  = Const(0, 8)
        s1 = Slice(c, 0, -1)
        self.assertEqual((s1.start, s1.stop), (0, 7))
        s1 = Slice(c, -4, -1)
        self.assertEqual((s1.start, s1.stop), (4, 7))

    def test_start_end_bool(self):
        c  = Const(0, 8)
        s  = Slice(c, False, True)
        self.assertIs(type(s.start), int)
        self.assertIs(type(s.stop),  int)

    def test_start_end_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Slice start must be an integer, not 'x'$"):
            Slice(0, "x", 1)
        with self.assertRaisesRegex(TypeError,
                r"^Slice stop must be an integer, not 'x'$"):
            Slice(0, 1, "x")

    def test_start_end_out_of_range(self):
        c = Const(0, 8)
        with self.assertRaisesRegex(IndexError,
                r"^Cannot start slice 10 bits into 8-bit value$"):
            Slice(c, 10, 12)
        with self.assertRaisesRegex(IndexError,
                r"^Cannot stop slice 12 bits into 8-bit value$"):
            Slice(c, 0, 12)
        with self.assertRaisesRegex(IndexError,
                r"^Slice start 4 must be less than slice stop 2$"):
            Slice(c, 4, 2)
        with self.assertRaisesRegex(IndexError,
                r"^Cannot start slice -9 bits into 8-bit value$"):
            Slice(c, -9, -5)

    def test_repr(self):
        s1 = Const(10)[2]
        self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)")


class BitSelectTestCase(FHDLTestCase):
    def setUp(self):
        self.c = Const(0, 8)
        self.s = Signal(range(self.c.width))

    def test_shape(self):
        s1 = self.c.bit_select(self.s, 2)
        self.assertIsInstance(s1, Part)
        self.assertEqual(s1.shape(), unsigned(2))
        self.assertIsInstance(s1.shape(), Shape)
        s2 = self.c.bit_select(self.s, 0)
        self.assertIsInstance(s2, Part)
        self.assertEqual(s2.shape(), unsigned(0))

    def test_stride(self):
        s1 = self.c.bit_select(self.s, 2)
        self.assertIsInstance(s1, Part)
        self.assertEqual(s1.stride, 1)

    def test_const(self):
        s1 = self.c.bit_select(1, 2)
        self.assertIsInstance(s1, Slice)
        self.assertRepr(s1, """(slice (const 8'd0) 1:3)""")

    def test_width_wrong(self):
        with self.assertRaises(TypeError):
            self.c.bit_select(self.s, -1)

    def test_repr(self):
        s = self.c.bit_select(self.s, 2)
        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)")


class WordSelectTestCase(FHDLTestCase):
    def setUp(self):
        self.c = Const(0, 8)
        self.s = Signal(range(self.c.width))

    def test_shape(self):
        s1 = self.c.word_select(self.s, 2)
        self.assertIsInstance(s1, Part)
        self.assertEqual(s1.shape(), unsigned(2))
        self.assertIsInstance(s1.shape(), Shape)

    def test_stride(self):
        s1 = self.c.word_select(self.s, 2)
        self.assertIsInstance(s1, Part)
        self.assertEqual(s1.stride, 2)

    def test_const(self):
        s1 = self.c.word_select(1, 2)
        self.assertIsInstance(s1, Slice)
        self.assertRepr(s1, """(slice (const 8'd0) 2:4)""")

    def test_width_wrong(self):
        with self.assertRaises(TypeError):
            self.c.word_select(self.s, 0)
        with self.assertRaises(TypeError):
            self.c.word_select(self.s, -1)

    def test_repr(self):
        s = self.c.word_select(self.s, 2)
        self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)")


class CatTestCase(FHDLTestCase):
    def test_shape(self):
        c0 = Cat()
        self.assertEqual(c0.shape(), unsigned(0))
        self.assertIsInstance(c0.shape(), Shape)
        c1 = Cat(Const(10))
        self.assertEqual(c1.shape(), unsigned(4))
        c2 = Cat(Const(10), Const(1))
        self.assertEqual(c2.shape(), unsigned(5))
        c3 = Cat(Const(10), Const(1), Const(0))
        self.assertEqual(c3.shape(), unsigned(6))

    def test_repr(self):
        c1 = Cat(Const(10), Const(1))
        self.assertEqual(repr(c1), "(cat (const 4'd10) (const 1'd1))")

    def test_cast(self):
        c = Cat(1, 0)
        self.assertEqual(repr(c), "(cat (const 1'd1) (const 1'd0))")
    
    def test_str_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object 'foo' cannot be converted to an Amaranth value$"):
            Cat("foo")

    def test_int_01(self):
        with warnings.catch_warnings():
            warnings.filterwarnings(action="error", category=SyntaxWarning)
            Cat(0, 1, 1, 0)

    def test_enum_wrong(self):
        class Color(Enum):
            RED  = 1
            BLUE = 2
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Argument #1 of Cat\(\) is an enumerated value <Color\.RED: 1> without "
                r"a defined shape used in bit vector context; define the enumeration by "
                r"inheriting from the class in amaranth\.lib\.enum and specifying "
                r"the 'shape=' keyword argument$"):
            c = Cat(Color.RED, Color.BLUE)
        self.assertEqual(repr(c), "(cat (const 2'd1) (const 2'd2))")

    def test_intenum_wrong(self):
        class Color(int, Enum):
            RED  = 1
            BLUE = 2
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Argument #1 of Cat\(\) is an enumerated value <Color\.RED: 1> without "
                r"a defined shape used in bit vector context; define the enumeration by "
                r"inheriting from the class in amaranth\.lib\.enum and specifying "
                r"the 'shape=' keyword argument$"):
            c = Cat(Color.RED, Color.BLUE)
        self.assertEqual(repr(c), "(cat (const 2'd1) (const 2'd2))")

    def test_int_wrong(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Argument #1 of Cat\(\) is a bare integer 2 used in bit vector context; "
                r"specify the width explicitly using C\(2, 2\)$"):
            Cat(2)


class ReplTestCase(FHDLTestCase):
    @_ignore_deprecated
    def test_cast(self):
        r = Repl(0, 3)
        self.assertEqual(repr(r), "(cat (const 1'd0) (const 1'd0) (const 1'd0))")

    @_ignore_deprecated
    def test_int_01(self):
        with warnings.catch_warnings():
            warnings.filterwarnings(action="error", category=SyntaxWarning)
            Repl(0, 3)
            Repl(1, 3)

    @_ignore_deprecated
    def test_int_wrong(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value argument of Repl\(\) is a bare integer 2 used in bit vector context; "
                r"consider specifying explicit width using C\(2, 2\) instead$"):
            Repl(2, 3)


class ArrayTestCase(FHDLTestCase):
    def test_acts_like_array(self):
        a = Array([1,2,3])
        self.assertSequenceEqual(a, [1,2,3])
        self.assertEqual(a[1], 2)
        a[1] = 4
        self.assertSequenceEqual(a, [1,4,3])
        del a[1]
        self.assertSequenceEqual(a, [1,3])
        a.insert(1, 2)
        self.assertSequenceEqual(a, [1,2,3])

    def test_becomes_immutable(self):
        a = Array([1,2,3])
        s1 = Signal(range(len(a)))
        s2 = Signal(range(len(a)))
        v1 = a[s1]
        v2 = a[s2]
        with self.assertRaisesRegex(ValueError,
                r"^Array can no longer be mutated after it was indexed with a value at "):
            a[1] = 2
        with self.assertRaisesRegex(ValueError,
                r"^Array can no longer be mutated after it was indexed with a value at "):
            del a[1]
        with self.assertRaisesRegex(ValueError,
                r"^Array can no longer be mutated after it was indexed with a value at "):
            a.insert(1, 2)

    def test_repr(self):
        a = Array([1,2,3])
        self.assertEqual(repr(a), "(array mutable [1, 2, 3])")
        s = Signal(range(len(a)))
        v = a[s]
        self.assertEqual(repr(a), "(array [1, 2, 3])")


class ArrayProxyTestCase(FHDLTestCase):
    def test_index_shape(self):
        m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4))
        a = Signal(range(3))
        b = Signal(range(3))
        v = m[a][b]
        self.assertEqual(v.shape(), unsigned(4))

    def test_attr_shape(self):
        from collections import namedtuple
        pair = namedtuple("pair", ("p", "n"))
        a = Array(pair(i, -i) for i in range(10))
        s = Signal(range(len(a)))
        v = a[s]
        self.assertEqual(v.p.shape(), unsigned(4))
        self.assertEqual(v.n.shape(), signed(5))

    def test_attr_shape_signed(self):
        # [unsigned(1), unsigned(1)] → unsigned(1)
        a1 = Array([1, 1])
        v1 = a1[Const(0)]
        self.assertEqual(v1.shape(), unsigned(1))
        # [signed(1), signed(1)] → signed(1)
        a2 = Array([-1, -1])
        v2 = a2[Const(0)]
        self.assertEqual(v2.shape(), signed(1))
        # [unsigned(1), signed(2)] → signed(2)
        a3 = Array([1, -2])
        v3 = a3[Const(0)]
        self.assertEqual(v3.shape(), signed(2))
        # [unsigned(1), signed(1)] → signed(2); 1st operand padded with sign bit!
        a4 = Array([1, -1])
        v4 = a4[Const(0)]
        self.assertEqual(v4.shape(), signed(2))
        # [unsigned(2), signed(1)] → signed(3); 1st operand padded with sign bit!
        a5 = Array([1, -1])
        v5 = a5[Const(0)]
        self.assertEqual(v5.shape(), signed(2))

    def test_repr(self):
        a = Array([1, 2, 3])
        s = Signal(range(3))
        v = a[s]
        self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))")


class SignalTestCase(FHDLTestCase):
    def test_shape(self):
        s1 = Signal()
        self.assertEqual(s1.shape(), unsigned(1))
        self.assertIsInstance(s1.shape(), Shape)
        s2 = Signal(2)
        self.assertEqual(s2.shape(), unsigned(2))
        s3 = Signal(unsigned(2))
        self.assertEqual(s3.shape(), unsigned(2))
        s4 = Signal(signed(2))
        self.assertEqual(s4.shape(), signed(2))
        s5 = Signal(0)
        self.assertEqual(s5.shape(), unsigned(0))
        s6 = Signal(range(16))
        self.assertEqual(s6.shape(), unsigned(4))
        s7 = Signal(range(4, 16))
        self.assertEqual(s7.shape(), unsigned(4))
        s8 = Signal(range(-4, 16))
        self.assertEqual(s8.shape(), signed(5))
        s9 = Signal(range(-20, 16))
        self.assertEqual(s9.shape(), signed(6))
        with warnings.catch_warnings():
            warnings.filterwarnings(action="ignore", category=SyntaxWarning)
            s10 = Signal(range(0))
            self.assertEqual(s10.shape(), unsigned(0))
        s11 = Signal(range(1))
        self.assertEqual(s11.shape(), unsigned(1))

    def test_shape_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Width must be a non-negative integer, not -10$"):
            Signal(-10)

    def test_name(self):
        s1 = Signal()
        self.assertEqual(s1.name, "s1")
        s2 = Signal(name="sig")
        self.assertEqual(s2.name, "sig")

    def test_reset(self):
        s1 = Signal(4, reset=0b111, reset_less=True)
        self.assertEqual(s1.reset, 0b111)
        self.assertEqual(s1.reset_less, True)

    def test_reset_enum(self):
        s1 = Signal(2, reset=UnsignedEnum.BAR)
        self.assertEqual(s1.reset, 2)
        with self.assertRaisesRegex(TypeError,
                r"^Reset value must be a constant-castable expression, "
                r"not <StringEnum\.FOO: 'a'>$"):
            Signal(1, reset=StringEnum.FOO)

    def test_reset_shape_castable_const(self):
        class CastableFromHex(ShapeCastable):
            def as_shape(self):
                return unsigned(8)

            def __call__(self, value):
                return value

            def const(self, init):
                return int(init, 16)

        s1 = Signal(CastableFromHex(), reset="aa")
        self.assertEqual(s1.reset, 0xaa)

        with self.assertRaisesRegex(ValueError,
                r"^Constant returned by <.+?CastableFromHex.+?>\.const\(\) must have the shape "
                r"that it casts to, unsigned\(8\), and not unsigned\(1\)$"):
            Signal(CastableFromHex(), reset="01")

    def test_reset_shape_castable_enum_wrong(self):
        class EnumA(AmaranthEnum):
            X = 1
        with self.assertRaisesRegex(TypeError,
                r"^Reset value must be a constant initializer of <enum 'EnumA'>$"):
            Signal(EnumA) # implied reset=0

    def test_reset_signed_mismatch(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Reset value -2 is signed, but the signal shape is unsigned\(2\)$"):
            Signal(unsigned(2), reset=-2)

    def test_reset_wrong_too_wide(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Reset value 2 will be truncated to the signal shape unsigned\(1\)$"):
            Signal(unsigned(1), reset=2)
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Reset value 1 will be truncated to the signal shape signed\(1\)$"):
            Signal(signed(1), reset=1)
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Reset value -2 will be truncated to the signal shape signed\(1\)$"):
            Signal(signed(1), reset=-2)

    def test_reset_wrong_fencepost(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Reset value 10 equals the non-inclusive end of the signal shape "
                r"range\(0, 10\); this is likely an off-by-one error$"):
            Signal(range(0, 10), reset=10)

    def test_attrs(self):
        s1 = Signal()
        self.assertEqual(s1.attrs, {})
        s2 = Signal(attrs={"no_retiming": True})
        self.assertEqual(s2.attrs, {"no_retiming": True})

    def test_repr(self):
        s1 = Signal()
        self.assertEqual(repr(s1), "(sig s1)")

    def test_like(self):
        s1 = Signal.like(Signal(4))
        self.assertEqual(s1.shape(), unsigned(4))
        s2 = Signal.like(Signal(range(-15, 1)))
        self.assertEqual(s2.shape(), signed(5))
        s3 = Signal.like(Signal(4, reset=0b111, reset_less=True))
        self.assertEqual(s3.reset, 0b111)
        self.assertEqual(s3.reset_less, True)
        s4 = Signal.like(Signal(attrs={"no_retiming": True}))
        self.assertEqual(s4.attrs, {"no_retiming": True})
        s5 = Signal.like(Signal(decoder=str))
        self.assertEqual(s5.decoder, str)
        s6 = Signal.like(10)
        self.assertEqual(s6.shape(), unsigned(4))
        s7 = [Signal.like(Signal(4))][0]
        self.assertEqual(s7.name, "$like")
        s8 = Signal.like(s1, name_suffix="_ff")
        self.assertEqual(s8.name, "s1_ff")

    def test_decoder(self):
        class Color(Enum):
            RED  = 1
            BLUE = 2
        s = Signal(decoder=Color)
        self.assertEqual(s.decoder(1), "RED/1")
        self.assertEqual(s.decoder(3), "3")

    def test_enum(self):
        s1 = Signal(UnsignedEnum)
        self.assertEqual(s1.shape(), unsigned(2))
        s2 = Signal(SignedEnum)
        self.assertEqual(s2.shape(), signed(2))
        self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")


class ClockSignalTestCase(FHDLTestCase):
    def test_domain(self):
        s1 = ClockSignal()
        self.assertEqual(s1.domain, "sync")
        s2 = ClockSignal("pix")
        self.assertEqual(s2.domain, "pix")

        with self.assertRaisesRegex(TypeError,
                r"^Clock domain name must be a string, not 1$"):
            ClockSignal(1)

    def test_shape(self):
        s1 = ClockSignal()
        self.assertEqual(s1.shape(), unsigned(1))
        self.assertIsInstance(s1.shape(), Shape)

    def test_repr(self):
        s1 = ClockSignal()
        self.assertEqual(repr(s1), "(clk sync)")

    def test_wrong_name_comb(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain 'comb' does not have a clock$"):
            ClockSignal("comb")


class ResetSignalTestCase(FHDLTestCase):
    def test_domain(self):
        s1 = ResetSignal()
        self.assertEqual(s1.domain, "sync")
        s2 = ResetSignal("pix")
        self.assertEqual(s2.domain, "pix")

        with self.assertRaisesRegex(TypeError,
                r"^Clock domain name must be a string, not 1$"):
            ResetSignal(1)

    def test_shape(self):
        s1 = ResetSignal()
        self.assertEqual(s1.shape(), unsigned(1))
        self.assertIsInstance(s1.shape(), Shape)

    def test_repr(self):
        s1 = ResetSignal()
        self.assertEqual(repr(s1), "(rst sync)")

    def test_wrong_name_comb(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain 'comb' does not have a reset$"):
            ResetSignal("comb")


class MockValueCastable(ValueCastable):
    def __init__(self, dest):
        self.dest = dest

    def shape(self):
        return Value.cast(self.dest).shape()

    @ValueCastable.lowermethod
    def as_value(self):
        return self.dest


class MockValueCastableChanges(ValueCastable):
    def __init__(self, width=0):
        self.width = width

    def shape(self):
        return unsigned(self.width)

    @ValueCastable.lowermethod
    def as_value(self):
        return Signal(self.width)


class MockValueCastableCustomGetattr(ValueCastable):
    def __init__(self):
        pass

    def shape(self):
        assert False

    @ValueCastable.lowermethod
    def as_value(self):
        return Const(0)

    def __getattr__(self, attr):
        assert False


class ValueCastableTestCase(FHDLTestCase):
    def test_not_decorated(self):
        with self.assertRaisesRegex(TypeError,
                r"^Class 'MockValueCastableNotDecorated' deriving from `ValueCastable` must "
                r"decorate the `as_value` method with the `ValueCastable.lowermethod` decorator$"):
            class MockValueCastableNotDecorated(ValueCastable):
                def __init__(self):
                    pass

                def shape(self):
                    pass

                def as_value(self):
                    return Signal()

    def test_no_override(self):
        with self.assertRaisesRegex(TypeError,
                r"^Class 'MockValueCastableNoOverrideAsValue' deriving from `ValueCastable` must "
                r"override the `as_value` method$"):
            class MockValueCastableNoOverrideAsValue(ValueCastable):
                def __init__(self):
                    pass

        with self.assertRaisesRegex(TypeError,
                r"^Class 'MockValueCastableNoOverrideShapec' deriving from `ValueCastable` must "
                r"override the `shape` method$"):
            class MockValueCastableNoOverrideShapec(ValueCastable):
                def __init__(self):
                    pass

                def as_value(self):
                    return Signal()

    def test_memoized(self):
        vc = MockValueCastableChanges(1)
        sig1 = vc.as_value()
        vc.width = 2
        sig2 = vc.as_value()
        self.assertIs(sig1, sig2)
        vc.width = 3
        sig3 = Value.cast(vc)
        self.assertIs(sig1, sig3)

    def test_custom_getattr(self):
        vc = MockValueCastableCustomGetattr()
        vc.as_value() # shouldn't call __getattr__

    def test_recurse_bad(self):
        vc = MockValueCastable(None)
        vc.dest = vc
        with self.assertRaisesRegex(RecursionError,
                r"^Value-castable object <.+> casts to itself$"):
            Value.cast(vc)

    def test_recurse(self):
        vc = MockValueCastable(MockValueCastable(Signal()))
        self.assertIsInstance(Value.cast(vc), Signal)


class SampleTestCase(FHDLTestCase):
    @_ignore_deprecated
    def test_const(self):
        s = Sample(1, 1, "sync")
        self.assertEqual(s.shape(), unsigned(1))

    @_ignore_deprecated
    def test_signal(self):
        s1 = Sample(Signal(2), 1, "sync")
        self.assertEqual(s1.shape(), unsigned(2))
        s2 = Sample(ClockSignal(), 1, "sync")
        s3 = Sample(ResetSignal(), 1, "sync")

    @_ignore_deprecated
    def test_wrong_value_operator(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Sampled value must be a signal or a constant, not "
                r"\(\+ \(sig \$signal\) \(const 1'd1\)\)$")):
            Sample(Signal() + 1, 1, "sync")

    @_ignore_deprecated
    def test_wrong_clocks_neg(self):
        with self.assertRaisesRegex(ValueError,
                r"^Cannot sample a value 1 cycles in the future$"):
            Sample(Signal(), -1, "sync")

    @_ignore_deprecated
    def test_wrong_domain(self):
        with self.assertRaisesRegex(TypeError,
                r"^Domain name must be a string or None, not 0$"):
            Sample(Signal(), 1, 0)


class InitialTestCase(FHDLTestCase):
    def test_initial(self):
        i = Initial()
        self.assertEqual(i.shape(), unsigned(1))


class SwitchTestCase(FHDLTestCase):
    def test_default_case(self):
        s = Switch(Const(0), {None: []})
        self.assertEqual(s.cases, {(): []})

    def test_int_case(self):
        s = Switch(Const(0, 8), {10: []})
        self.assertEqual(s.cases, {("00001010",): []})

    def test_int_neg_case(self):
        s = Switch(Const(0, 8), {-10: []})
        self.assertEqual(s.cases, {("11110110",): []})

    def test_enum_case(self):
        s = Switch(Const(0, UnsignedEnum), {UnsignedEnum.FOO: []})
        self.assertEqual(s.cases, {("01",): []})

    def test_str_case(self):
        s = Switch(Const(0, 8), {"0000 11\t01": []})
        self.assertEqual(s.cases, {("00001101",): []})

    def test_two_cases(self):
        s = Switch(Const(0, 8), {("00001111", 123): []})
        self.assertEqual(s.cases, {("00001111", "01111011"): []})


# tests/test_hdl_cd.py

from amaranth.hdl.cd import *

from .utils import *


class ClockDomainTestCase(FHDLTestCase):
    def test_name(self):
        sync = ClockDomain()
        self.assertEqual(sync.name, "sync")
        self.assertEqual(sync.clk.name, "clk")
        self.assertEqual(sync.rst.name, "rst")
        self.assertEqual(sync.local, False)
        pix = ClockDomain()
        self.assertEqual(pix.name, "pix")
        self.assertEqual(pix.clk.name, "pix_clk")
        self.assertEqual(pix.rst.name, "pix_rst")
        cd_pix = ClockDomain()
        self.assertEqual(cd_pix.name, "pix")
        dom = [ClockDomain("foo")][0]
        self.assertEqual(dom.name, "foo")
        with self.assertRaisesRegex(ValueError,
                r"^Clock domain name must be specified explicitly$"):
            ClockDomain()
        cd_reset = ClockDomain(local=True)
        self.assertEqual(cd_reset.local, True)

    def test_edge(self):
        sync = ClockDomain()
        self.assertEqual(sync.clk_edge, "pos")
        sync = ClockDomain(clk_edge="pos")
        self.assertEqual(sync.clk_edge, "pos")
        sync = ClockDomain(clk_edge="neg")
        self.assertEqual(sync.clk_edge, "neg")

    def test_edge_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"):
            ClockDomain("sync", clk_edge="xxx")

    def test_with_reset(self):
        pix = ClockDomain()
        self.assertIsNotNone(pix.clk)
        self.assertIsNotNone(pix.rst)
        self.assertFalse(pix.async_reset)

    def test_without_reset(self):
        pix = ClockDomain(reset_less=True)
        self.assertIsNotNone(pix.clk)
        self.assertIsNone(pix.rst)
        self.assertFalse(pix.async_reset)

    def test_async_reset(self):
        pix = ClockDomain(async_reset=True)
        self.assertIsNotNone(pix.clk)
        self.assertIsNotNone(pix.rst)
        self.assertTrue(pix.async_reset)

    def test_rename(self):
        sync = ClockDomain()
        self.assertEqual(sync.name, "sync")
        self.assertEqual(sync.clk.name, "clk")
        self.assertEqual(sync.rst.name, "rst")
        sync.rename("pix")
        self.assertEqual(sync.name, "pix")
        self.assertEqual(sync.clk.name, "pix_clk")
        self.assertEqual(sync.rst.name, "pix_rst")

    def test_rename_reset_less(self):
        sync = ClockDomain(reset_less=True)
        self.assertEqual(sync.name, "sync")
        self.assertEqual(sync.clk.name, "clk")
        sync.rename("pix")
        self.assertEqual(sync.name, "pix")
        self.assertEqual(sync.clk.name, "pix_clk")

    def test_wrong_name_comb(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain 'comb' may not be clocked$"):
            comb = ClockDomain()


# tests/test_hdl_dsl.py

# amaranth: UnusedElaboratable=no

import sys
from collections import OrderedDict

from amaranth.hdl.ast import *
from amaranth.hdl.cd import *
from amaranth.hdl.dsl import *
from amaranth.lib.enum import Enum

from .utils import *
from amaranth._utils import _ignore_deprecated


class DSLTestCase(FHDLTestCase):
    def setUp(self):
        self.s1 = Signal()
        self.s2 = Signal()
        self.s3 = Signal()
        self.c1 = Signal()
        self.c2 = Signal()
        self.c3 = Signal()
        self.w1 = Signal(4)

    def test_cant_inherit(self):
        with self.assertRaisesRegex(SyntaxError,
                (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and "
                    r"return a `Module` from the `elaborate\(self, platform\)` method$")):
            class ORGate(Module):
                pass

    def test_d_comb(self):
        m = Module()
        m.d.comb += self.c1.eq(1)
        m._flush()
        self.assertEqual(m._driving[self.c1], None)
        self.assertRepr(m._statements, """(
            (eq (sig c1) (const 1'd1))
        )""")

    def test_d_sync(self):
        m = Module()
        m.d.sync += self.c1.eq(1)
        m._flush()
        self.assertEqual(m._driving[self.c1], "sync")
        self.assertRepr(m._statements, """(
            (eq (sig c1) (const 1'd1))
        )""")

    def test_d_pix(self):
        m = Module()
        m.d.pix += self.c1.eq(1)
        m._flush()
        self.assertEqual(m._driving[self.c1], "pix")
        self.assertRepr(m._statements, """(
            (eq (sig c1) (const 1'd1))
        )""")

    def test_d_index(self):
        m = Module()
        m.d["pix"] += self.c1.eq(1)
        m._flush()
        self.assertEqual(m._driving[self.c1], "pix")
        self.assertRepr(m._statements, """(
            (eq (sig c1) (const 1'd1))
        )""")

    def test_d_no_conflict(self):
        m = Module()
        m.d.comb += self.w1[0].eq(1)
        m.d.comb += self.w1[1].eq(1)

    def test_d_conflict(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it "
                    r"is already driven from d\.comb$")):
            m.d.comb += self.c1.eq(1)
            m.d.sync += self.c1.eq(1)

    def test_d_wrong(self):
        m = Module()
        with self.assertRaisesRegex(AttributeError,
                r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"):
            m.d.pix = None

    def test_d_asgn_wrong(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^Only assignments and property checks may be appended to d\.sync$"):
            m.d.sync += Switch(self.s1, {})

    def test_comb_wrong(self):
        m = Module()
        with self.assertRaisesRegex(AttributeError,
                r"^'Module' object has no attribute 'comb'; did you mean 'd\.comb'\?$"):
            m.comb += self.c1.eq(1)

    def test_sync_wrong(self):
        m = Module()
        with self.assertRaisesRegex(AttributeError,
                r"^'Module' object has no attribute 'sync'; did you mean 'd\.sync'\?$"):
            m.sync += self.c1.eq(1)

    def test_attr_wrong(self):
        m = Module()
        with self.assertRaisesRegex(AttributeError,
                r"^'Module' object has no attribute 'nonexistentattr'$"):
            m.nonexistentattr

    def test_d_suspicious(self):
        m = Module()
        with self.assertWarnsRegex(SyntaxWarning,
                (r"^Using '<module>\.d\.submodules' would add statements to clock domain "
                    r"'submodules'; did you mean <module>\.submodules instead\?$")):
            m.d.submodules += []

    def test_clock_signal(self):
        m = Module()
        m.d.comb += ClockSignal("pix").eq(ClockSignal())
        self.assertRepr(m._statements, """
        (
            (eq (clk pix) (clk sync))
        )
        """)

    def test_reset_signal(self):
        m = Module()
        m.d.comb += ResetSignal("pix").eq(1)
        self.assertRepr(m._statements, """
        (
            (eq (rst pix) (const 1'd1))
        )
        """)

    @_ignore_deprecated
    def test_sample_domain(self):
        m = Module()
        i = Signal()
        o1 = Signal()
        o2 = Signal()
        o3 = Signal()
        m.d.sync += o1.eq(Past(i))
        m.d.pix  += o2.eq(Past(i))
        m.d.pix  += o3.eq(Past(i, domain="sync"))
        f = m.elaborate(platform=None)
        self.assertRepr(f.statements, """
        (
            (eq (sig o1) (sample (sig i) @ sync[1]))
            (eq (sig o2) (sample (sig i) @ pix[1]))
            (eq (sig o3) (sample (sig i) @ sync[1]))
        )
        """)

    def test_If(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1))
                (case 1 (eq (sig c1) (const 1'd1)))
            )
        )
        """)

    def test_If_Elif(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
        with m.Elif(self.s2):
            m.d.sync += self.c2.eq(0)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1) (sig s2))
                (case -1 (eq (sig c1) (const 1'd1)))
                (case 1- (eq (sig c2) (const 1'd0)))
            )
        )
        """)

    def test_If_Elif_Else(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
        with m.Elif(self.s2):
            m.d.sync += self.c2.eq(0)
        with m.Else():
            m.d.comb += self.c3.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1) (sig s2))
                (case -1 (eq (sig c1) (const 1'd1)))
                (case 1- (eq (sig c2) (const 1'd0)))
                (default (eq (sig c3) (const 1'd1)))
            )
        )
        """)

    def test_If_If(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
        with m.If(self.s2):
            m.d.comb += self.c2.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1))
                (case 1 (eq (sig c1) (const 1'd1)))
            )
            (switch (cat (sig s2))
                (case 1 (eq (sig c2) (const 1'd1)))
            )
        )
        """)

    def test_If_nested_If(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
            with m.If(self.s2):
                m.d.comb += self.c2.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1))
                (case 1 (eq (sig c1) (const 1'd1))
                    (switch (cat (sig s2))
                        (case 1 (eq (sig c2) (const 1'd1)))
                    )
                )
            )
        )
        """)

    def test_If_dangling_Else(self):
        m = Module()
        with m.If(self.s1):
            m.d.comb += self.c1.eq(1)
            with m.If(self.s2):
                m.d.comb += self.c2.eq(1)
        with m.Else():
            m.d.comb += self.c3.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (sig s1))
                (case 1
                    (eq (sig c1) (const 1'd1))
                    (switch (cat (sig s2))
                        (case 1 (eq (sig c2) (const 1'd1)))
                    )
                )
                (default
                    (eq (sig c3) (const 1'd1))
                )
            )
        )
        """)

    def test_Elif_wrong(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^Elif without preceding If$"):
            with m.Elif(self.s2):
                pass

    def test_Elif_wrong_nested(self):
        m = Module()
        with m.If(self.s1):
            with self.assertRaisesRegex(SyntaxError,
                    r"^Elif without preceding If$"):
                with m.Elif(self.s2):
                    pass

    def test_Else_wrong(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^Else without preceding If\/Elif$"):
            with m.Else():
                pass

    def test_Else_wrong_nested(self):
        m = Module()
        with m.If(self.s1):
            with self.assertRaisesRegex(SyntaxError,
                    r"^Else without preceding If/Elif$"):
                with m.Else():
                    pass

    def test_Elif_Elif_wrong_nested(self):
        m = Module()
        with m.If(self.s1):
            pass
        with m.Elif(self.s2):
            with self.assertRaisesRegex(SyntaxError,
                    r"^Elif without preceding If$"):
                with m.Elif(self.s3):
                    pass

    def test_Else_Else_wrong_nested(self):
        m = Module()
        with m.If(self.s1):
            pass
        with m.Else():
            with self.assertRaisesRegex(SyntaxError,
                    r"^Else without preceding If/Elif$"):
                with m.Else():
                    pass

    def test_If_wide(self):
        m = Module()
        with m.If(self.w1):
            m.d.comb += self.c1.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (cat (b (sig w1)))
                (case 1 (eq (sig c1) (const 1'd1)))
            )
        )
        """)

    if sys.version_info < (3, 12): # upstream warning in 3.12!
        def test_If_signed_suspicious(self):
            m = Module()
            with self.assertWarnsRegex(SyntaxWarning,
                    r"^Signed values in If\/Elif conditions usually result from inverting Python "
                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
                    r"`not flag`\. \(If this is a false positive, silence this warning with "
                    r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$"):
                with m.If(~True):
                    pass

        def test_Elif_signed_suspicious(self):
            m = Module()
            with m.If(0):
                pass
            with self.assertWarnsRegex(SyntaxWarning,
                    r"^Signed values in If\/Elif conditions usually result from inverting Python "
                    r"booleans with ~, which leads to unexpected results\. Replace `~flag` with "
                    r"`not flag`\. \(If this is a false positive, silence this warning with "
                    r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$"):
                with m.Elif(~True):
                    pass

    def test_if_If_Elif_Else(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"):
            if m.If(0):
                pass
        with m.If(0):
            pass
        with self.assertRaisesRegex(SyntaxError,
                r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"):
            if m.Elif(0):
                pass
        with self.assertRaisesRegex(SyntaxError,
                r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"):
            if m.Else():
                pass

    def test_Switch(self):
        m = Module()
        with m.Switch(self.w1):
            with m.Case(3):
                m.d.comb += self.c1.eq(1)
            with m.Case("11--"):
                m.d.comb += self.c2.eq(1)
            with m.Case("1 0--"):
                m.d.comb += self.c2.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (sig w1)
                (case 0011 (eq (sig c1) (const 1'd1)))
                (case 11-- (eq (sig c2) (const 1'd1)))
                (case 10-- (eq (sig c2) (const 1'd1)))
            )
        )
        """)

    def test_Switch_default_Case(self):
        m = Module()
        with m.Switch(self.w1):
            with m.Case(3):
                m.d.comb += self.c1.eq(1)
            with m.Case():
                m.d.comb += self.c2.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (sig w1)
                (case 0011 (eq (sig c1) (const 1'd1)))
                (default (eq (sig c2) (const 1'd1)))
            )
        )
        """)

    def test_Switch_default_Default(self):
        m = Module()
        with m.Switch(self.w1):
            with m.Case(3):
                m.d.comb += self.c1.eq(1)
            with m.Default():
                m.d.comb += self.c2.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (sig w1)
                (case 0011 (eq (sig c1) (const 1'd1)))
                (default (eq (sig c2) (const 1'd1)))
            )
        )
        """)

    def test_Switch_const_test(self):
        m = Module()
        with m.Switch(1):
            with m.Case(1):
                m.d.comb += self.c1.eq(1)
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (const 1'd1)
                (case 1 (eq (sig c1) (const 1'd1)))
            )
        )
        """)

    def test_Switch_enum(self):
        class Color(Enum):
            RED  = 1
            BLUE = 2
        m = Module()
        se = Signal(Color, reset=Color.RED)
        with m.Switch(se):
            with m.Case(Color.RED):
                m.d.comb += self.c1.eq(1)
        self.assertRepr(m._statements, """
        (
            (switch (sig se)
                (case 01 (eq (sig c1) (const 1'd1)))
            )
        )
        """)

    def test_Switch_const_castable(self):
        class Color(Enum, shape=1):
            RED  = 0
            BLUE = 1
        m = Module()
        se = Signal(2)
        with m.Switch(se):
            with m.Case(Cat(Color.RED, Color.BLUE)):
                m.d.comb += self.c1.eq(1)
        self.assertRepr(m._statements, """
        (
            (switch (sig se)
                (case 10 (eq (sig c1) (const 1'd1)))
            )
        )
        """)

    def test_Case_width_wrong(self):
        class Color(Enum):
            RED = 0b10101010
        m = Module()
        with m.Switch(self.w1):
            with self.assertRaisesRegex(SyntaxError,
                    r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"):
                with m.Case("--"):
                    pass
            with self.assertWarnsRegex(SyntaxWarning,
                    r"^Case pattern '22' \(5'10110\) is wider than switch value \(which has "
                    r"width 4\); comparison will never be true$"):
                with m.Case(0b10110):
                    pass
            with self.assertWarnsRegex(SyntaxWarning,
                    r"^Case pattern '<Color.RED: 170>' \(8'10101010\) is wider than switch value "
                    r"\(which has width 4\); comparison will never be true$"):
                with m.Case(Color.RED):
                    pass
        self.assertRepr(m._statements, """
        (
            (switch (sig w1) )
        )
        """)

    def test_Case_bits_wrong(self):
        m = Module()
        with m.Switch(self.w1):
            with self.assertRaisesRegex(SyntaxError,
                    (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, "
                        r"and may include whitespace$")):
                with m.Case("abc"):
                    pass

    def test_Case_pattern_wrong(self):
        m = Module()
        with m.Switch(self.w1):
            with self.assertRaisesRegex(SyntaxError,
                    r"^Case pattern must be a string or a constant-castable expression, "
                    r"not 1\.0$"):
                with m.Case(1.0):
                    pass

    def test_Case_outside_Switch_wrong(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^Case is not permitted outside of Switch$"):
            with m.Case():
                pass

    def test_If_inside_Switch_wrong(self):
        m = Module()
        with m.Switch(self.s1):
            with self.assertRaisesRegex(SyntaxError,
                    (r"^If is not permitted directly inside of Switch; "
                        r"it is permitted inside of Switch Case$")):
                with m.If(self.s2):
                    pass

    def test_Case_wrong_nested(self):
        m = Module()
        with m.Switch(self.s1):
            with m.Case(0):
                with self.assertRaisesRegex(SyntaxError,
                    r"^Case is not permitted outside of Switch$"):
                    with m.Case(1):
                        pass

    def test_FSM_basic(self):
        a = Signal()
        b = Signal()
        c = Signal()
        m = Module()
        with m.FSM():
            with m.State("FIRST"):
                m.d.comb += a.eq(1)
                m.next = "SECOND"
            with m.State("SECOND"):
                m.d.sync += b.eq(~b)
                with m.If(c):
                    m.next = "FIRST"
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (sig fsm_state)
                (case 0
                    (eq (sig a) (const 1'd1))
                    (eq (sig fsm_state) (const 1'd1))
                )
                (case 1
                    (eq (sig b) (~ (sig b)))
                    (switch (cat (sig c))
                        (case 1
                            (eq (sig fsm_state) (const 1'd0)))
                    )
                )
            )
        )
        """)
        self.assertEqual({repr(k): v for k, v in m._driving.items()}, {
            "(sig a)": None,
            "(sig fsm_state)": "sync",
            "(sig b)": "sync",
        })

        frag = m.elaborate(platform=None)
        fsm  = frag.find_generated("fsm")
        self.assertIsInstance(fsm.state, Signal)
        self.assertEqual(fsm.encoding, OrderedDict({
            "FIRST": 0,
            "SECOND": 1,
        }))
        self.assertEqual(fsm.decoding, OrderedDict({
            0: "FIRST",
            1: "SECOND"
        }))

    def test_FSM_reset(self):
        a = Signal()
        m = Module()
        with m.FSM(reset="SECOND"):
            with m.State("FIRST"):
                m.d.comb += a.eq(0)
                m.next = "SECOND"
            with m.State("SECOND"):
                m.next = "FIRST"
        m._flush()
        self.assertRepr(m._statements, """
        (
            (switch (sig fsm_state)
                (case 0
                    (eq (sig a) (const 1'd0))
                    (eq (sig fsm_state) (const 1'd1))
                )
                (case 1
                    (eq (sig fsm_state) (const 1'd0))
                )
            )
        )
        """)

    def test_FSM_ongoing(self):
        a = Signal()
        b = Signal()
        m = Module()
        with m.FSM() as fsm:
            m.d.comb += b.eq(fsm.ongoing("SECOND"))
            with m.State("FIRST"):
                pass
            m.d.comb += a.eq(fsm.ongoing("FIRST"))
            with m.State("SECOND"):
                pass
        m._flush()
        self.assertEqual(m._generated["fsm"].state.reset, 1)
        self.maxDiff = 10000
        self.assertRepr(m._statements, """
        (
            (eq (sig b) (== (sig fsm_state) (const 1'd0)))
            (eq (sig a) (== (sig fsm_state) (const 1'd1)))
            (switch (sig fsm_state)
                (case 1
                )
                (case 0
                )
            )
        )
        """)

    def test_FSM_empty(self):
        m = Module()
        with m.FSM():
            pass
        self.assertRepr(m._statements, """
        ()
        """)

    def test_FSM_wrong_domain(self):
        m = Module()
        with self.assertRaisesRegex(ValueError,
                r"^FSM may not be driven by the 'comb' domain$"):
            with m.FSM(domain="comb"):
                pass

    def test_FSM_wrong_undefined(self):
        m = Module()
        with self.assertRaisesRegex(NameError,
                r"^FSM state 'FOO' is referenced but not defined$"):
            with m.FSM() as fsm:
                fsm.ongoing("FOO")

    def test_FSM_wrong_redefined(self):
        m = Module()
        with m.FSM():
            with m.State("FOO"):
                pass
            with self.assertRaisesRegex(NameError,
                    r"^FSM state 'FOO' is already defined$"):
                with m.State("FOO"):
                    pass

    def test_FSM_wrong_next(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
                r"^Only assignment to `m\.next` is permitted$"):
            m.next
        with self.assertRaisesRegex(SyntaxError,
                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
            m.next = "FOO"
        with self.assertRaisesRegex(SyntaxError,
                r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"):
            with m.FSM():
                m.next = "FOO"

    def test_If_inside_FSM_wrong(self):
        m = Module()
        with m.FSM():
            with m.State("FOO"):
                pass
            with self.assertRaisesRegex(SyntaxError,
                    (r"^If is not permitted directly inside of FSM; "
                        r"it is permitted inside of FSM State$")):
                with m.If(self.s2):
                    pass

    def test_State_outside_FSM_wrong(self):
        m = Module()
        with self.assertRaisesRegex(SyntaxError,
            r"^FSM State is not permitted outside of FSM"):
            with m.State("FOO"):
                pass


    def test_FSM_State_wrong_nested(self):
        m = Module()
        with m.FSM():
            with m.State("FOO"):
                with self.assertRaisesRegex(SyntaxError,
                    r"^FSM State is not permitted outside of FSM"):
                    with m.State("BAR"):
                        pass

    def test_auto_pop_ctrl(self):
        m = Module()
        with m.If(self.w1):
            m.d.comb += self.c1.eq(1)
        m.d.comb += self.c2.eq(1)
        self.assertRepr(m._statements, """
        (
            (switch (cat (b (sig w1)))
                (case 1 (eq (sig c1) (const 1'd1)))
            )
            (eq (sig c2) (const 1'd1))
        )
        """)

    def test_submodule_anon(self):
        m1 = Module()
        m2 = Module()
        m1.submodules += m2
        self.assertEqual(m1._anon_submodules, [m2])
        self.assertEqual(m1._named_submodules, {})

    def test_submodule_anon_multi(self):
        m1 = Module()
        m2 = Module()
        m3 = Module()
        m1.submodules += m2, m3
        self.assertEqual(m1._anon_submodules, [m2, m3])
        self.assertEqual(m1._named_submodules, {})

    def test_submodule_named(self):
        m1 = Module()
        m2 = Module()
        m1.submodules.foo = m2
        self.assertEqual(m1._anon_submodules, [])
        self.assertEqual(m1._named_submodules, {"foo": m2})

    def test_submodule_named_index(self):
        m1 = Module()
        m2 = Module()
        m1.submodules["foo"] = m2
        self.assertEqual(m1._anon_submodules, [])
        self.assertEqual(m1._named_submodules, {"foo": m2})

    def test_submodule_wrong(self):
        m = Module()
        with self.assertRaisesRegex(TypeError,
                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
            m.submodules.foo = 1
        with self.assertRaisesRegex(TypeError,
                r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"):
            m.submodules += 1

    def test_submodule_named_conflict(self):
        m1 = Module()
        m2 = Module()
        m1.submodules.foo = m2
        with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"):
            m1.submodules.foo = m2

    def test_submodule_get(self):
        m1 = Module()
        m2 = Module()
        m1.submodules.foo = m2
        m3 = m1.submodules.foo
        self.assertEqual(m2, m3)

    def test_submodule_get_index(self):
        m1 = Module()
        m2 = Module()
        m1.submodules["foo"] = m2
        m3 = m1.submodules["foo"]
        self.assertEqual(m2, m3)

    def test_submodule_get_unset(self):
        m1 = Module()
        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
            m2 = m1.submodules.foo
        with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"):
            m2 = m1.submodules["foo"]

    def test_domain_named_implicit(self):
        m = Module()
        m.domains += ClockDomain("sync")
        self.assertEqual(len(m._domains), 1)

    def test_domain_named_explicit(self):
        m = Module()
        m.domains.foo = ClockDomain()
        self.assertEqual(len(m._domains), 1)
        self.assertEqual(m._domains["foo"].name, "foo")

    def test_domain_add_wrong(self):
        m = Module()
        with self.assertRaisesRegex(TypeError,
                r"^Only clock domains may be added to `m\.domains`, not 1$"):
            m.domains.foo = 1
        with self.assertRaisesRegex(TypeError,
                r"^Only clock domains may be added to `m\.domains`, not 1$"):
            m.domains += 1

    def test_domain_add_wrong_name(self):
        m = Module()
        with self.assertRaisesRegex(NameError,
                r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"):
            m.domains.foo = ClockDomain("bar")

    def test_domain_add_wrong_duplicate(self):
        m = Module()
        m.domains += ClockDomain("foo")
        with self.assertRaisesRegex(NameError,
                r"^Clock domain named 'foo' already exists$"):
            m.domains += ClockDomain("foo")

    def test_lower(self):
        m1 = Module()
        m1.d.comb += self.c1.eq(self.s1)
        m2 = Module()
        m2.d.comb += self.c2.eq(self.s2)
        m2.d.sync += self.c3.eq(self.s3)
        m1.submodules.foo = m2

        f1 = m1.elaborate(platform=None)
        self.assertRepr(f1.statements, """
        (
            (eq (sig c1) (sig s1))
        )
        """)
        self.assertEqual(f1.drivers, {
            None: SignalSet((self.c1,))
        })
        self.assertEqual(len(f1.subfragments), 1)
        (f2, f2_name), = f1.subfragments
        self.assertEqual(f2_name, "foo")
        self.assertRepr(f2.statements, """
        (
            (eq (sig c2) (sig s2))
            (eq (sig c3) (sig s3))
        )
        """)
        self.assertEqual(f2.drivers, {
            None: SignalSet((self.c2,)),
            "sync": SignalSet((self.c3,))
        })
        self.assertEqual(len(f2.subfragments), 0)


# tests/test_hdl_ir.py

# amaranth: UnusedElaboratable=no

from collections import OrderedDict

from amaranth.hdl.ast import *
from amaranth.hdl.cd import *
from amaranth.hdl.ir import *
from amaranth.hdl.mem import *

from .utils import *


class ElaboratesToNone(Elaboratable):
    def elaborate(self, platform):
        return


class ElaboratesToSelf(Elaboratable):
    def elaborate(self, platform):
        return self


class FragmentGetTestCase(FHDLTestCase):
    def test_get_wrong_none(self):
        with self.assertRaisesRegex(AttributeError,
                r"^Object None cannot be elaborated$"):
            Fragment.get(None, platform=None)

        with self.assertWarnsRegex(UserWarning,
                r"^\.elaborate\(\) returned None; missing return statement\?$"):
            with self.assertRaisesRegex(AttributeError,
                    r"^Object None cannot be elaborated$"):
                Fragment.get(ElaboratesToNone(), platform=None)

    def test_get_wrong_self(self):
        with self.assertRaisesRegex(RecursionError,
                r"^Object <.+?ElaboratesToSelf.+?> elaborates to itself$"):
            Fragment.get(ElaboratesToSelf(), platform=None)


class FragmentGeneratedTestCase(FHDLTestCase):
    def test_find_subfragment(self):
        f1 = Fragment()
        f2 = Fragment()
        f1.add_subfragment(f2, "f2")

        self.assertEqual(f1.find_subfragment(0), f2)
        self.assertEqual(f1.find_subfragment("f2"), f2)

    def test_find_subfragment_wrong(self):
        f1 = Fragment()
        f2 = Fragment()
        f1.add_subfragment(f2, "f2")

        with self.assertRaisesRegex(NameError,
                r"^No subfragment at index #1$"):
            f1.find_subfragment(1)
        with self.assertRaisesRegex(NameError,
                r"^No subfragment with name 'fx'$"):
            f1.find_subfragment("fx")

    def test_find_generated(self):
        f1 = Fragment()
        f2 = Fragment()
        f2.generated["sig"] = sig = Signal()
        f1.add_subfragment(f2, "f2")

        self.assertEqual(SignalKey(f1.find_generated("f2", "sig")),
                         SignalKey(sig))


class FragmentDriversTestCase(FHDLTestCase):
    def test_empty(self):
        f = Fragment()
        self.assertEqual(list(f.iter_comb()), [])
        self.assertEqual(list(f.iter_sync()), [])


class FragmentPortsTestCase(FHDLTestCase):
    def setUp(self):
        self.s1 = Signal()
        self.s2 = Signal()
        self.s3 = Signal()
        self.c1 = Signal()
        self.c2 = Signal()
        self.c3 = Signal()

    def test_empty(self):
        f = Fragment()
        self.assertEqual(list(f.iter_ports()), [])

        f._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([]))

    def test_iter_signals(self):
        f = Fragment()
        f.add_ports(self.s1, self.s2, dir="io")
        self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals())

    def test_self_contained(self):
        f = Fragment()
        f.add_statements(
            self.c1.eq(self.s1),
            self.s1.eq(self.c1)
        )

        f._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([]))

    def test_infer_input(self):
        f = Fragment()
        f.add_statements(
            self.c1.eq(self.s1)
        )

        f._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([
            (self.s1, "i")
        ]))

    def test_request_output(self):
        f = Fragment()
        f.add_statements(
            self.c1.eq(self.s1)
        )

        f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([
            (self.s1, "i"),
            (self.c1, "o")
        ]))

    def test_input_in_subfragment(self):
        f1 = Fragment()
        f1.add_statements(
            self.c1.eq(self.s1)
        )
        f2 = Fragment()
        f2.add_statements(
            self.s1.eq(0)
        )
        f1.add_subfragment(f2)
        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict())
        self.assertEqual(f2.ports, SignalDict([
            (self.s1, "o"),
        ]))

    def test_input_only_in_subfragment(self):
        f1 = Fragment()
        f2 = Fragment()
        f2.add_statements(
            self.c1.eq(self.s1)
        )
        f1.add_subfragment(f2)
        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict([
            (self.s1, "i"),
        ]))
        self.assertEqual(f2.ports, SignalDict([
            (self.s1, "i"),
        ]))

    def test_output_from_subfragment(self):
        f1 = Fragment()
        f1.add_statements(
            self.c1.eq(0)
        )
        f2 = Fragment()
        f2.add_statements(
            self.c2.eq(1)
        )
        f1.add_subfragment(f2)

        f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict([
            (self.c2, "o"),
        ]))
        self.assertEqual(f2.ports, SignalDict([
            (self.c2, "o"),
        ]))

    def test_output_from_subfragment_2(self):
        f1 = Fragment()
        f1.add_statements(
            self.c1.eq(self.s1)
        )
        f2 = Fragment()
        f2.add_statements(
            self.c2.eq(self.s1)
        )
        f1.add_subfragment(f2)
        f3 = Fragment()
        f3.add_statements(
            self.s1.eq(0)
        )
        f2.add_subfragment(f3)

        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f2.ports, SignalDict([
            (self.s1, "o"),
        ]))

    def test_input_output_sibling(self):
        f1 = Fragment()
        f2 = Fragment()
        f2.add_statements(
            self.c1.eq(self.c2)
        )
        f1.add_subfragment(f2)
        f3 = Fragment()
        f3.add_statements(
            self.c2.eq(0)
        )
        f3.add_driver(self.c2)
        f1.add_subfragment(f3)

        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict())

    def test_output_input_sibling(self):
        f1 = Fragment()
        f2 = Fragment()
        f2.add_statements(
            self.c2.eq(0)
        )
        f2.add_driver(self.c2)
        f1.add_subfragment(f2)
        f3 = Fragment()
        f3.add_statements(
            self.c1.eq(self.c2)
        )
        f1.add_subfragment(f3)

        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict())

    def test_input_cd(self):
        sync = ClockDomain()
        f = Fragment()
        f.add_statements(
            self.c1.eq(self.s1)
        )
        f.add_domains(sync)
        f.add_driver(self.c1, "sync")

        f._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([
            (self.s1,  "i"),
            (sync.clk, "i"),
            (sync.rst, "i"),
        ]))

    def test_input_cd_reset_less(self):
        sync = ClockDomain(reset_less=True)
        f = Fragment()
        f.add_statements(
            self.c1.eq(self.s1)
        )
        f.add_domains(sync)
        f.add_driver(self.c1, "sync")

        f._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f.ports, SignalDict([
            (self.s1,  "i"),
            (sync.clk, "i"),
        ]))

    def test_inout(self):
        s = Signal()
        f1 = Fragment()
        f2 = Instance("foo", io_x=s)
        f1.add_subfragment(f2)

        f1._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict([
            (s, "io")
        ]))

    def test_in_out_same_signal(self):
        s = Signal()

        f1 = Instance("foo", i_x=s, o_y=s)
        f2 = Fragment()
        f2.add_subfragment(f1)

        f2._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f1.ports, SignalDict([
            (s, "o")
        ]))

        f3 = Instance("foo", o_y=s, i_x=s)
        f4 = Fragment()
        f4.add_subfragment(f3)

        f4._propagate_ports(ports=(), all_undef_as_ports=True)
        self.assertEqual(f3.ports, SignalDict([
            (s, "o")
        ]))

    def test_clk_rst(self):
        sync = ClockDomain()
        f = Fragment()
        f.add_domains(sync)

        f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync")))
        self.assertEqual(f.ports, SignalDict([
            (sync.clk, "i"),
            (sync.rst, "i"),
        ]))

    def test_port_wrong(self):
        f = Fragment()
        with self.assertRaisesRegex(TypeError,
                r"^Only signals may be added as ports, not \(const 1'd1\)$"):
            f.prepare(ports=(Const(1),))

    def test_port_not_iterable(self):
        f = Fragment()
        with self.assertRaisesRegex(TypeError,
                r"^`ports` must be either a list or a tuple, not 1$"):
            f.prepare(ports=1)
        with self.assertRaisesRegex(TypeError,
                (r"^`ports` must be either a list or a tuple, not \(const 1'd1\)"
                    r" \(did you mean `ports=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
            f.prepare(ports=Const(1))

class FragmentDomainsTestCase(FHDLTestCase):
    def test_iter_signals(self):
        cd1 = ClockDomain()
        cd2 = ClockDomain(reset_less=True)
        s1 = Signal()
        s2 = Signal()

        f = Fragment()
        f.add_domains(cd1, cd2)
        f.add_driver(s1, "cd1")
        self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals())
        f.add_driver(s2, "cd2")
        self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals())

    def test_propagate_up(self):
        cd = ClockDomain()

        f1 = Fragment()
        f2 = Fragment()
        f1.add_subfragment(f2)
        f2.add_domains(cd)

        f1._propagate_domains_up()
        self.assertEqual(f1.domains, {"cd": cd})

    def test_propagate_up_local(self):
        cd = ClockDomain(local=True)

        f1 = Fragment()
        f2 = Fragment()
        f1.add_subfragment(f2)
        f2.add_domains(cd)

        f1._propagate_domains_up()
        self.assertEqual(f1.domains, {})

    def test_domain_conflict(self):
        cda = ClockDomain("sync")
        cdb = ClockDomain("sync")

        fa = Fragment()
        fa.add_domains(cda)
        fb = Fragment()
        fb.add_domains(cdb)
        f = Fragment()
        f.add_subfragment(fa, "a")
        f.add_subfragment(fb, "b")

        f._propagate_domains_up()
        self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb})
        (fa, _), (fb, _) = f.subfragments
        self.assertEqual(fa.domains, {"a_sync": cda})
        self.assertEqual(fb.domains, {"b_sync": cdb})

    def test_domain_conflict_anon(self):
        cda = ClockDomain("sync")
        cdb = ClockDomain("sync")

        fa = Fragment()
        fa.add_domains(cda)
        fb = Fragment()
        fb.add_domains(cdb)
        f = Fragment()
        f.add_subfragment(fa, "a")
        f.add_subfragment(fb)

        with self.assertRaisesRegex(DomainError,
                (r"^Domain 'sync' is defined by subfragments 'a', <unnamed #1> of fragment "
                    r"'top'; it is necessary to either rename subfragment domains explicitly, "
                    r"or give names to subfragments$")):
            f._propagate_domains_up()

    def test_domain_conflict_name(self):
        cda = ClockDomain("sync")
        cdb = ClockDomain("sync")

        fa = Fragment()
        fa.add_domains(cda)
        fb = Fragment()
        fb.add_domains(cdb)
        f = Fragment()
        f.add_subfragment(fa, "x")
        f.add_subfragment(fb, "x")

        with self.assertRaisesRegex(DomainError,
                (r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some "
                    r"of which have identical names; it is necessary to either rename subfragment "
                    r"domains explicitly, or give distinct names to subfragments$")):
            f._propagate_domains_up()

    def test_domain_conflict_rename_drivers(self):
        cda = ClockDomain("sync")
        cdb = ClockDomain("sync")

        fa = Fragment()
        fa.add_domains(cda)
        fb = Fragment()
        fb.add_domains(cdb)
        fb.add_driver(ResetSignal("sync"), None)
        f = Fragment()
        f.add_subfragment(fa, "a")
        f.add_subfragment(fb, "b")

        f._propagate_domains_up()
        fb_new, _ = f.subfragments[1]
        self.assertEqual(fb_new.drivers, OrderedDict({
            None: SignalSet((ResetSignal("b_sync"),))
        }))

    def test_domain_conflict_rename_drivers_before_creating_missing(self):
        cda = ClockDomain("sync")
        cdb = ClockDomain("sync")
        s = Signal()

        fa = Fragment()
        fa.add_domains(cda)
        fb = Fragment()
        fb.add_domains(cdb)
        f = Fragment()
        f.add_subfragment(fa, "a")
        f.add_subfragment(fb, "b")
        f.add_driver(s, "b_sync")

        f._propagate_domains(lambda name: ClockDomain(name))

    def test_propagate_down(self):
        cd = ClockDomain()

        f1 = Fragment()
        f2 = Fragment()
        f1.add_domains(cd)
        f1.add_subfragment(f2)

        f1._propagate_domains_down()
        self.assertEqual(f2.domains, {"cd": cd})

    def test_propagate_down_idempotent(self):
        cd = ClockDomain()

        f1 = Fragment()
        f1.add_domains(cd)
        f2 = Fragment()
        f2.add_domains(cd)
        f1.add_subfragment(f2)

        f1._propagate_domains_down()
        self.assertEqual(f1.domains, {"cd": cd})
        self.assertEqual(f2.domains, {"cd": cd})

    def test_propagate(self):
        cd = ClockDomain()

        f1 = Fragment()
        f2 = Fragment()
        f1.add_domains(cd)
        f1.add_subfragment(f2)

        new_domains = f1._propagate_domains(missing_domain=lambda name: None)
        self.assertEqual(f1.domains, {"cd": cd})
        self.assertEqual(f2.domains, {"cd": cd})
        self.assertEqual(new_domains, [])

    def test_propagate_missing(self):
        s1 = Signal()
        f1 = Fragment()
        f1.add_driver(s1, "sync")

        with self.assertRaisesRegex(DomainError,
                r"^Domain 'sync' is used but not defined$"):
            f1._propagate_domains(missing_domain=lambda name: None)

    def test_propagate_create_missing(self):
        s1 = Signal()
        f1 = Fragment()
        f1.add_driver(s1, "sync")
        f2 = Fragment()
        f1.add_subfragment(f2)

        new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name))
        self.assertEqual(f1.domains.keys(), {"sync"})
        self.assertEqual(f2.domains.keys(), {"sync"})
        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
        self.assertEqual(new_domains, [f1.domains["sync"]])

    def test_propagate_create_missing_fragment(self):
        s1 = Signal()
        f1 = Fragment()
        f1.add_driver(s1, "sync")

        cd = ClockDomain("sync")
        f2 = Fragment()
        f2.add_domains(cd)

        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
        self.assertEqual(f1.domains.keys(), {"sync"})
        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
        self.assertEqual(new_domains, [])
        self.assertEqual(f1.subfragments, [
            (f2, "cd_sync")
        ])

    def test_propagate_create_missing_fragment_many_domains(self):
        s1 = Signal()
        f1 = Fragment()
        f1.add_driver(s1, "sync")

        cd_por  = ClockDomain("por")
        cd_sync = ClockDomain("sync")
        f2 = Fragment()
        f2.add_domains(cd_por, cd_sync)

        new_domains = f1._propagate_domains(missing_domain=lambda name: f2)
        self.assertEqual(f1.domains.keys(), {"sync", "por"})
        self.assertEqual(f2.domains.keys(), {"sync", "por"})
        self.assertEqual(f1.domains["sync"], f2.domains["sync"])
        self.assertEqual(new_domains, [])
        self.assertEqual(f1.subfragments, [
            (f2, "cd_sync")
        ])

    def test_propagate_create_missing_fragment_wrong(self):
        s1 = Signal()
        f1 = Fragment()
        f1.add_driver(s1, "sync")

        f2 = Fragment()
        f2.add_domains(ClockDomain("foo"))

        with self.assertRaisesRegex(DomainError,
                (r"^Fragment returned by missing domain callback does not define requested "
                    r"domain 'sync' \(defines 'foo'\)\.$")):
            f1._propagate_domains(missing_domain=lambda name: f2)


class FragmentHierarchyConflictTestCase(FHDLTestCase):
    def setUp_self_sub(self):
        self.s1 = Signal()
        self.c1 = Signal()
        self.c2 = Signal()

        self.f1 = Fragment()
        self.f1.add_statements(self.c1.eq(0))
        self.f1.add_driver(self.s1)
        self.f1.add_driver(self.c1, "sync")

        self.f1a = Fragment()
        self.f1.add_subfragment(self.f1a, "f1a")

        self.f2 = Fragment()
        self.f2.add_statements(self.c2.eq(1))
        self.f2.add_driver(self.s1)
        self.f2.add_driver(self.c2, "sync")
        self.f1.add_subfragment(self.f2)

        self.f1b = Fragment()
        self.f1.add_subfragment(self.f1b, "f1b")

        self.f2a = Fragment()
        self.f2.add_subfragment(self.f2a, "f2a")

    def test_conflict_self_sub(self):
        self.setUp_self_sub()

        self.f1._resolve_hierarchy_conflicts(mode="silent")
        self.assertEqual(self.f1.subfragments, [
            (self.f1a, "f1a"),
            (self.f1b, "f1b"),
            (self.f2a, "f2a"),
        ])
        self.assertRepr(self.f1.statements, """
        (
            (eq (sig c1) (const 1'd0))
            (eq (sig c2) (const 1'd1))
        )
        """)
        self.assertEqual(self.f1.drivers, {
            None:   SignalSet((self.s1,)),
            "sync": SignalSet((self.c1, self.c2)),
        })

    def test_conflict_self_sub_error(self):
        self.setUp_self_sub()

        with self.assertRaisesRegex(DriverConflict,
               r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>$"):
            self.f1._resolve_hierarchy_conflicts(mode="error")

    def test_conflict_self_sub_warning(self):
        self.setUp_self_sub()

        with self.assertWarnsRegex(DriverConflict,
                (r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.<unnamed #1>; "
                    r"hierarchy will be flattened$")):
            self.f1._resolve_hierarchy_conflicts(mode="warn")

    def setUp_sub_sub(self):
        self.s1 = Signal()
        self.c1 = Signal()
        self.c2 = Signal()

        self.f1 = Fragment()

        self.f2 = Fragment()
        self.f2.add_driver(self.s1)
        self.f2.add_statements(self.c1.eq(0))
        self.f1.add_subfragment(self.f2)

        self.f3 = Fragment()
        self.f3.add_driver(self.s1)
        self.f3.add_statements(self.c2.eq(1))
        self.f1.add_subfragment(self.f3)

    def test_conflict_sub_sub(self):
        self.setUp_sub_sub()

        self.f1._resolve_hierarchy_conflicts(mode="silent")
        self.assertEqual(self.f1.subfragments, [])
        self.assertRepr(self.f1.statements, """
        (
            (eq (sig c1) (const 1'd0))
            (eq (sig c2) (const 1'd1))
        )
        """)

    def setUp_self_subsub(self):
        self.s1 = Signal()
        self.c1 = Signal()
        self.c2 = Signal()

        self.f1 = Fragment()
        self.f1.add_driver(self.s1)

        self.f2 = Fragment()
        self.f2.add_statements(self.c1.eq(0))
        self.f1.add_subfragment(self.f2)

        self.f3 = Fragment()
        self.f3.add_driver(self.s1)
        self.f3.add_statements(self.c2.eq(1))
        self.f2.add_subfragment(self.f3)

    def test_conflict_self_subsub(self):
        self.setUp_self_subsub()

        self.f1._resolve_hierarchy_conflicts(mode="silent")
        self.assertEqual(self.f1.subfragments, [])
        self.assertRepr(self.f1.statements, """
        (
            (eq (sig c1) (const 1'd0))
            (eq (sig c2) (const 1'd1))
        )
        """)

    def setUp_memory(self):
        self.m = Memory(width=8, depth=4)
        self.fr = self.m.read_port().elaborate(platform=None)
        self.fw = self.m.write_port().elaborate(platform=None)
        self.f1 = Fragment()
        self.f2 = Fragment()
        self.f2.add_subfragment(self.fr)
        self.f1.add_subfragment(self.f2)
        self.f3 = Fragment()
        self.f3.add_subfragment(self.fw)
        self.f1.add_subfragment(self.f3)

    def test_conflict_memory(self):
        self.setUp_memory()

        self.f1._resolve_hierarchy_conflicts(mode="silent")
        self.assertEqual(self.f1.subfragments, [
            (self.fr, None),
            (self.fw, None),
        ])

    def test_conflict_memory_error(self):
        self.setUp_memory()

        with self.assertRaisesRegex(DriverConflict,
                r"^Memory 'm' is accessed from multiple fragments: top\.<unnamed #0>, "
                    r"top\.<unnamed #1>$"):
            self.f1._resolve_hierarchy_conflicts(mode="error")

    def test_conflict_memory_warning(self):
        self.setUp_memory()

        with self.assertWarnsRegex(DriverConflict,
                (r"^Memory 'm' is accessed from multiple fragments: top.<unnamed #0>, "
                    r"top.<unnamed #1>; hierarchy will be flattened$")):
            self.f1._resolve_hierarchy_conflicts(mode="warn")

    def test_explicit_flatten(self):
        self.f1 = Fragment()
        self.f2 = Fragment()
        self.f2.flatten = True
        self.f1.add_subfragment(self.f2)

        self.f1._resolve_hierarchy_conflicts(mode="silent")
        self.assertEqual(self.f1.subfragments, [])

    def test_no_conflict_local_domains(self):
        f1 = Fragment()
        cd1 = ClockDomain("d", local=True)
        f1.add_domains(cd1)
        f1.add_driver(ClockSignal("d"))
        f2 = Fragment()
        cd2 = ClockDomain("d", local=True)
        f2.add_domains(cd2)
        f2.add_driver(ClockSignal("d"))
        f3 = Fragment()
        f3.add_subfragment(f1)
        f3.add_subfragment(f2)
        f3.prepare()


class InstanceTestCase(FHDLTestCase):
    def test_construct(self):
        s1 = Signal()
        s2 = Signal()
        s3 = Signal()
        s4 = Signal()
        s5 = Signal()
        s6 = Signal()
        inst = Instance("foo",
            ("a", "ATTR1", 1),
            ("p", "PARAM1", 0x1234),
            ("i", "s1", s1),
            ("o", "s2", s2),
            ("io", "s3", s3),
            a_ATTR2=2,
            p_PARAM2=0x5678,
            i_s4=s4,
            o_s5=s5,
            io_s6=s6,
        )
        self.assertEqual(inst.attrs, OrderedDict([
            ("ATTR1", 1),
            ("ATTR2", 2),
        ]))
        self.assertEqual(inst.parameters, OrderedDict([
            ("PARAM1", 0x1234),
            ("PARAM2", 0x5678),
        ]))
        self.assertEqual(inst.named_ports, OrderedDict([
            ("s1", (s1, "i")),
            ("s2", (s2, "o")),
            ("s3", (s3, "io")),
            ("s4", (s4, "i")),
            ("s5", (s5, "o")),
            ("s6", (s6, "io")),
        ]))

    def test_cast_ports(self):
        inst = Instance("foo",
            ("i", "s1", 1),
            ("o", "s2", 2),
            ("io", "s3", 3),
            i_s4=4,
            o_s5=5,
            io_s6=6,
        )
        self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)")
        self.assertRepr(inst.named_ports["s2"][0], "(const 2'd2)")
        self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)")
        self.assertRepr(inst.named_ports["s4"][0], "(const 3'd4)")
        self.assertRepr(inst.named_ports["s5"][0], "(const 3'd5)")
        self.assertRepr(inst.named_ports["s6"][0], "(const 3'd6)")

    def test_wrong_construct_arg(self):
        s = Signal()
        with self.assertRaisesRegex(NameError,
                (r"^Instance argument \('', 's1', \(sig s\)\) should be a tuple "
                    r"\(kind, name, value\) where kind is one of \"a\", \"p\", \"i\", \"o\", or \"io\"$")):
            Instance("foo", ("", "s1", s))

    def test_wrong_construct_kwarg(self):
        s = Signal()
        with self.assertRaisesRegex(NameError,
                (r"^Instance keyword argument x_s1=\(sig s\) does not start with one of "
                    r"\"a_\", \"p_\", \"i_\", \"o_\", or \"io_\"$")):
            Instance("foo", x_s1=s)

    def setUp_cpu(self):
        self.rst = Signal()
        self.stb = Signal()
        self.pins = Signal(8)
        self.datal = Signal(4)
        self.datah = Signal(4)
        self.inst = Instance("cpu",
            p_RESET=0x1234,
            i_clk=ClockSignal(),
            i_rst=self.rst,
            o_stb=self.stb,
            o_data=Cat(self.datal, self.datah),
            io_pins=self.pins[:]
        )
        self.wrap = Fragment()
        self.wrap.add_subfragment(self.inst)

    def test_init(self):
        self.setUp_cpu()
        f = self.inst
        self.assertEqual(f.type, "cpu")
        self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)]))
        self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"])
        self.assertEqual(f.ports, SignalDict([]))

    def test_prepare(self):
        self.setUp_cpu()
        f = self.wrap.prepare()
        sync_clk = f.domains["sync"].clk
        self.assertEqual(f.ports, SignalDict([
            (sync_clk, "i"),
            (self.rst, "i"),
            (self.pins, "io"),
        ]))

    def test_prepare_explicit_ports(self):
        self.setUp_cpu()
        f = self.wrap.prepare(ports=[self.rst, self.stb])
        sync_clk = f.domains["sync"].clk
        sync_rst = f.domains["sync"].rst
        self.assertEqual(f.ports, SignalDict([
            (sync_clk, "i"),
            (sync_rst, "i"),
            (self.rst, "i"),
            (self.stb, "o"),
            (self.pins, "io"),
        ]))

    def test_prepare_slice_in_port(self):
        s = Signal(2)
        f = Fragment()
        f.add_subfragment(Instance("foo", o_O=s[0]))
        f.add_subfragment(Instance("foo", o_O=s[1]))
        fp = f.prepare(ports=[s], missing_domain=lambda name: None)
        self.assertEqual(fp.ports, SignalDict([
            (s, "o"),
        ]))

    def test_prepare_attrs(self):
        self.setUp_cpu()
        self.inst.attrs["ATTR"] = 1
        f = self.inst.prepare()
        self.assertEqual(f.attrs, OrderedDict([
            ("ATTR", 1),
        ]))


# tests/test_hdl_mem.py

# amaranth: UnusedElaboratable=no

from amaranth.hdl.ast import *
from amaranth.hdl.mem import *

from .utils import *


class MemoryTestCase(FHDLTestCase):
    def test_name(self):
        m1 = Memory(width=8, depth=4)
        self.assertEqual(m1.name, "m1")
        m2 = [Memory(width=8, depth=4)][0]
        self.assertEqual(m2.name, "$memory")
        m3 = Memory(width=8, depth=4, name="foo")
        self.assertEqual(m3.name, "foo")

    def test_geometry(self):
        m = Memory(width=8, depth=4)
        self.assertEqual(m.width, 8)
        self.assertEqual(m.depth, 4)

    def test_geometry_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Memory width must be a non-negative integer, not -1$"):
            m = Memory(width=-1, depth=4)
        with self.assertRaisesRegex(TypeError,
                r"^Memory depth must be a non-negative integer, not -1$"):
            m = Memory(width=8, depth=-1)

    def test_init(self):
        m = Memory(width=8, depth=4, init=range(4))
        self.assertEqual(m.init, [0, 1, 2, 3])

    def test_init_wrong_count(self):
        with self.assertRaisesRegex(ValueError,
                r"^Memory initialization value count exceed memory depth \(8 > 4\)$"):
            m = Memory(width=8, depth=4, init=range(8))

    def test_init_wrong_type(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Memory initialization value at address 1: "
                    r"'str' object cannot be interpreted as an integer$")):
            m = Memory(width=8, depth=4, init=[1, "0"])

    def test_attrs(self):
        m1 = Memory(width=8, depth=4)
        self.assertEqual(m1.attrs, {})
        m2 = Memory(width=8, depth=4, attrs={"ram_block": True})
        self.assertEqual(m2.attrs, {"ram_block": True})

    def test_read_port_transparent(self):
        mem    = Memory(width=8, depth=4)
        rdport = mem.read_port()
        self.assertEqual(rdport.memory, mem)
        self.assertEqual(rdport.domain, "sync")
        self.assertEqual(rdport.transparent, True)
        self.assertEqual(len(rdport.addr), 2)
        self.assertEqual(len(rdport.data), 8)
        self.assertEqual(len(rdport.en), 1)
        self.assertIsInstance(rdport.en, Const)
        self.assertEqual(rdport.en.value, 1)

    def test_read_port_non_transparent(self):
        mem    = Memory(width=8, depth=4)
        rdport = mem.read_port(transparent=False)
        self.assertEqual(rdport.memory, mem)
        self.assertEqual(rdport.domain, "sync")
        self.assertEqual(rdport.transparent, False)
        self.assertEqual(len(rdport.en), 1)
        self.assertIsInstance(rdport.en, Signal)
        self.assertEqual(rdport.en.reset, 1)

    def test_read_port_asynchronous(self):
        mem    = Memory(width=8, depth=4)
        rdport = mem.read_port(domain="comb")
        self.assertEqual(rdport.memory, mem)
        self.assertEqual(rdport.domain, "comb")
        self.assertEqual(rdport.transparent, True)
        self.assertEqual(len(rdport.en), 1)
        self.assertIsInstance(rdport.en, Const)
        self.assertEqual(rdport.en.value, 1)

    def test_read_port_wrong(self):
        mem = Memory(width=8, depth=4)
        with self.assertRaisesRegex(ValueError,
                r"^Read port cannot be simultaneously asynchronous and non-transparent$"):
            mem.read_port(domain="comb", transparent=False)

    def test_write_port(self):
        mem    = Memory(width=8, depth=4)
        wrport = mem.write_port()
        self.assertEqual(wrport.memory, mem)
        self.assertEqual(wrport.domain, "sync")
        self.assertEqual(wrport.granularity, 8)
        self.assertEqual(len(wrport.addr), 2)
        self.assertEqual(len(wrport.data), 8)
        self.assertEqual(len(wrport.en), 1)

    def test_write_port_granularity(self):
        mem    = Memory(width=8, depth=4)
        wrport = mem.write_port(granularity=2)
        self.assertEqual(wrport.memory, mem)
        self.assertEqual(wrport.domain, "sync")
        self.assertEqual(wrport.granularity, 2)
        self.assertEqual(len(wrport.addr), 2)
        self.assertEqual(len(wrport.data), 8)
        self.assertEqual(len(wrport.en), 4)

    def test_write_port_granularity_wrong(self):
        mem = Memory(width=8, depth=4)
        with self.assertRaisesRegex(TypeError,
                r"^Write port granularity must be a non-negative integer, not -1$"):
            mem.write_port(granularity=-1)
        with self.assertRaisesRegex(ValueError,
                r"^Write port granularity must not be greater than memory width \(10 > 8\)$"):
            mem.write_port(granularity=10)
        with self.assertRaisesRegex(ValueError,
                r"^Write port granularity must divide memory width evenly$"):
            mem.write_port(granularity=3)


class DummyPortTestCase(FHDLTestCase):
    def test_name(self):
        p1 = DummyPort(data_width=8, addr_width=2)
        self.assertEqual(p1.addr.name, "p1_addr")
        p2 = [DummyPort(data_width=8, addr_width=2)][0]
        self.assertEqual(p2.addr.name, "dummy_addr")
        p3 = DummyPort(data_width=8, addr_width=2, name="foo")
        self.assertEqual(p3.addr.name, "foo_addr")

    def test_sizes(self):
        p1 = DummyPort(data_width=8, addr_width=2)
        self.assertEqual(p1.addr.width, 2)
        self.assertEqual(p1.data.width, 8)
        self.assertEqual(p1.en.width, 1)
        p2 = DummyPort(data_width=8, addr_width=2, granularity=2)
        self.assertEqual(p2.en.width, 4)


# tests/test_hdl_rec.py

from enum import Enum

from amaranth.hdl.ast import *
from amaranth.hdl.rec import *

from .utils import *


class UnsignedEnum(Enum):
    FOO = 1
    BAR = 2
    BAZ = 3


class LayoutTestCase(FHDLTestCase):
    def assertFieldEqual(self, field, expected):
        (shape, dir) = field
        shape = Shape.cast(shape)
        self.assertEqual((shape, dir), expected)

    def test_fields(self):
        layout = Layout.cast([
            ("cyc",  1),
            ("data", signed(32)),
            ("stb",  1, DIR_FANOUT),
            ("ack",  1, DIR_FANIN),
            ("info", [
                ("a", 1),
                ("b", 1),
            ])
        ])

        self.assertFieldEqual(layout["cyc"], (unsigned(1), DIR_NONE))
        self.assertFieldEqual(layout["data"], (signed(32), DIR_NONE))
        self.assertFieldEqual(layout["stb"], (unsigned(1), DIR_FANOUT))
        self.assertFieldEqual(layout["ack"], (unsigned(1), DIR_FANIN))
        sublayout = layout["info"][0]
        self.assertEqual(layout["info"][1], DIR_NONE)
        self.assertFieldEqual(sublayout["a"], (unsigned(1), DIR_NONE))
        self.assertFieldEqual(sublayout["b"], (unsigned(1), DIR_NONE))

    def test_enum_field(self):
        layout = Layout.cast([
            ("enum", UnsignedEnum),
            ("enum_dir", UnsignedEnum, DIR_FANOUT),
        ])
        self.assertFieldEqual(layout["enum"], (unsigned(2), DIR_NONE))
        self.assertFieldEqual(layout["enum_dir"], (unsigned(2), DIR_FANOUT))

    def test_range_field(self):
        layout = Layout.cast([
            ("range", range(0, 7)),
        ])
        self.assertFieldEqual(layout["range"], (unsigned(3), DIR_NONE))

    def test_slice_tuple(self):
        layout = Layout.cast([
            ("a", 1),
            ("b", 2),
            ("c", 3)
        ])
        expect = Layout.cast([
            ("a", 1),
            ("c", 3)
        ])
        self.assertEqual(layout["a", "c"], expect)

    def test_repr(self):
        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])),
                         "Layout([('a', unsigned(1)), ('b', signed(2))])")
        self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])),
                         "Layout([('a', unsigned(1)), "
                            "('b', Layout([('c', signed(3))]))])")

    def test_wrong_field(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or "
                    r"\(name, shape, direction\)$")):
            Layout.cast([(1,)])

    def test_wrong_name(self):
        with self.assertRaisesRegex(TypeError,
                r"^Field \(1, 1\) has invalid name: should be a string$"):
            Layout.cast([(1, 1)])

    def test_wrong_name_duplicate(self):
        with self.assertRaisesRegex(NameError,
                r"^Field \('a', 2\) has a name that is already present in the layout$"):
            Layout.cast([("a", 1), ("a", 2)])

    def test_wrong_direction(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction "
                    r"instance like DIR_FANIN$")):
            Layout.cast([("a", 1, 0)])

    def test_wrong_shape(self):
        with self.assertRaisesRegex(TypeError,
                (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or "
                    r"a list of fields of a nested record$")):
            Layout.cast([("a", "x")])


class RecordTestCase(FHDLTestCase):
    def test_basic(self):
        r = Record([
            ("stb",  1),
            ("data", 32),
            ("info", [
                ("a", 1),
                ("b", 1),
            ])
        ])

        self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))")
        self.assertEqual(len(r),  35)
        self.assertIsInstance(r.stb, Signal)
        self.assertEqual(r.stb.name, "r__stb")
        self.assertEqual(r["stb"].name, "r__stb")

        self.assertTrue(hasattr(r, "stb"))
        self.assertFalse(hasattr(r, "xxx"))

    def test_unnamed(self):
        r = [Record([
            ("stb", 1)
        ])][0]

        self.assertEqual(repr(r), "(rec <unnamed> stb)")
        self.assertEqual(r.stb.name, "stb")

    def test_iter(self):
        r = Record([
            ("data", 4),
            ("stb",  1),
        ])

        self.assertEqual(repr(r[0]),   "(slice (cat (sig r__data) (sig r__stb)) 0:1)")
        self.assertEqual(repr(r[0:3]), "(slice (cat (sig r__data) (sig r__stb)) 0:3)")

    def test_wrong_field(self):
        r = Record([
            ("stb", 1),
            ("ack", 1),
        ])
        with self.assertRaisesRegex(AttributeError,
                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
            r["en"]
        with self.assertRaisesRegex(AttributeError,
                r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
            r.en

    def test_wrong_field_unnamed(self):
        r = [Record([
            ("stb", 1),
            ("ack", 1),
        ])][0]
        with self.assertRaisesRegex(AttributeError,
                r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"):
            r.en

    def test_construct_with_fields(self):
        ns = Signal(1)
        nr = Record([
            ("burst", 1)
        ])
        r = Record([
            ("stb", 1),
            ("info", [
                ("burst", 1)
            ])
        ], fields={
            "stb":  ns,
            "info": nr
        })
        self.assertIs(r.stb, ns)
        self.assertIs(r.info, nr)

    def test_shape(self):
        r1 = Record([("a", 1), ("b", 2)])
        self.assertEqual(r1.shape(), unsigned(3))

    def test_like(self):
        r1 = Record([("a", 1), ("b", 2)])
        r2 = Record.like(r1)
        self.assertEqual(r1.layout, r2.layout)
        self.assertEqual(r2.name, "r2")
        r3 = Record.like(r1, name="foo")
        self.assertEqual(r3.name, "foo")
        r4 = Record.like(r1, name_suffix="foo")
        self.assertEqual(r4.name, "r1foo")

    def test_like_modifications(self):
        r1 = Record([("a", 1), ("b", [("s", 1)])])
        self.assertEqual(r1.a.name, "r1__a")
        self.assertEqual(r1.b.name, "r1__b")
        self.assertEqual(r1.b.s.name, "r1__b__s")
        r1.a.reset = 1
        r1.b.s.reset = 1
        r2 = Record.like(r1)
        self.assertEqual(r2.a.reset, 1)
        self.assertEqual(r2.b.s.reset, 1)
        self.assertEqual(r2.a.name, "r2__a")
        self.assertEqual(r2.b.name, "r2__b")
        self.assertEqual(r2.b.s.name, "r2__b__s")

    def test_slice_tuple(self):
        r1 = Record([("a", 1), ("b", 2), ("c", 3)])
        r2 = r1["a", "c"]
        self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)]))
        self.assertIs(r2.a, r1.a)
        self.assertIs(r2.c, r1.c)

    def test_enum_decoder(self):
        r1 = Record([("a", UnsignedEnum)])
        self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")

    def test_operators(self):
        r1 = Record([("a", 1)])
        s1 = Signal()

        # __bool__
        with self.assertRaisesRegex(TypeError,
                r"^Attempted to convert Amaranth value to Python boolean$"):
            not r1

        # __invert__, __neg__
        self.assertEqual(repr(~r1), "(~ (cat (sig r1__a)))")
        self.assertEqual(repr(-r1), "(- (cat (sig r1__a)))")

        # __add__, __radd__, __sub__, __rsub__
        self.assertEqual(repr(r1 + 1),  "(+ (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 + s1), "(+ (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 + r1),  "(+ (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 + r1), "(+ (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 - 1),  "(- (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 - s1), "(- (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 - r1),  "(- (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 - r1), "(- (sig s1) (cat (sig r1__a)))")

        # __mul__, __rmul__
        self.assertEqual(repr(r1 * 1),  "(* (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 * s1), "(* (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 * r1),  "(* (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 * r1), "(* (sig s1) (cat (sig r1__a)))")

        # __mod__, __rmod__, __floordiv__, __rfloordiv__
        self.assertEqual(repr(r1 % 1),   "(% (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 % s1),  "(% (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 % r1),   "(% (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 % r1),  "(% (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 // 1),  "(// (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 // s1), "(// (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 // r1),  "(// (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 // r1), "(// (sig s1) (cat (sig r1__a)))")

        # __lshift__, __rlshift__, __rshift__, __rrshift__
        self.assertEqual(repr(r1 >> 1),  "(>> (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 >> s1), "(>> (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 >> r1),  "(>> (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 >> r1), "(>> (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 << 1),  "(<< (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 << s1), "(<< (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 << r1),  "(<< (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 << r1), "(<< (sig s1) (cat (sig r1__a)))")

        # __and__, __rand__, __xor__, __rxor__, __or__, __ror__
        self.assertEqual(repr(r1 & 1),  "(& (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 & s1), "(& (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 & r1),  "(& (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 & r1), "(& (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 ^ 1),  "(^ (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 ^ s1), "(^ (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 ^ r1),  "(^ (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 ^ r1), "(^ (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 | 1),  "(| (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 | s1), "(| (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(1 | r1),  "(| (const 1'd1) (cat (sig r1__a)))")
        self.assertEqual(repr(s1 | r1), "(| (sig s1) (cat (sig r1__a)))")

        # __eq__, __ne__, __lt__, __le__, __gt__, __ge__
        self.assertEqual(repr(r1 == 1),  "(== (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 == s1), "(== (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 == r1), "(== (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 != 1),  "(!= (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 != s1), "(!= (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 != r1), "(!= (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 < 1),   "(< (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 < s1),  "(< (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 < r1),  "(< (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 <= 1),  "(<= (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 <= s1), "(<= (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 <= r1), "(<= (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 > 1),   "(> (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 > s1),  "(> (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 > r1),  "(> (sig s1) (cat (sig r1__a)))")
        self.assertEqual(repr(r1 >= 1),  "(>= (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1 >= s1), "(>= (cat (sig r1__a)) (sig s1))")
        self.assertEqual(repr(s1 >= r1), "(>= (sig s1) (cat (sig r1__a)))")

        # __abs__, __len__
        self.assertEqual(repr(abs(r1)), "(cat (sig r1__a))")
        self.assertEqual(len(r1), 1)

        # as_unsigned, as_signed, bool, any, all, xor, implies
        self.assertEqual(repr(r1.as_unsigned()), "(u (cat (sig r1__a)))")
        self.assertEqual(repr(r1.as_signed()),   "(s (cat (sig r1__a)))")
        self.assertEqual(repr(r1.bool()),        "(b (cat (sig r1__a)))")
        self.assertEqual(repr(r1.any()),         "(r| (cat (sig r1__a)))")
        self.assertEqual(repr(r1.all()),         "(r& (cat (sig r1__a)))")
        self.assertEqual(repr(r1.xor()),         "(r^ (cat (sig r1__a)))")
        self.assertEqual(repr(r1.implies(1)),    "(| (~ (cat (sig r1__a))) (const 1'd1))")
        self.assertEqual(repr(r1.implies(s1)),   "(| (~ (cat (sig r1__a))) (sig s1))")

        # bit_select, word_select, matches,
        self.assertEqual(repr(r1.bit_select(0, 1)),  "(slice (cat (sig r1__a)) 0:1)")
        self.assertEqual(repr(r1.word_select(0, 1)), "(slice (cat (sig r1__a)) 0:1)")
        self.assertEqual(repr(r1.matches("1")),
                "(== (& (cat (sig r1__a)) (const 1'd1)) (const 1'd1))")

        # shift_left, shift_right, rotate_left, rotate_right, eq
        self.assertEqual(repr(r1.shift_left(1)),  "(cat (const 1'd0) (cat (sig r1__a)))")
        self.assertEqual(repr(r1.shift_right(1)), "(slice (cat (sig r1__a)) 1:1)")
        self.assertEqual(repr(r1.rotate_left(1)), "(cat (slice (cat (sig r1__a)) 0:1) (slice (cat (sig r1__a)) 0:0))")
        self.assertEqual(repr(r1.rotate_right(1)), "(cat (slice (cat (sig r1__a)) 0:1) (slice (cat (sig r1__a)) 0:0))")
        self.assertEqual(repr(r1.eq(1)), "(eq (cat (sig r1__a)) (const 1'd1))")
        self.assertEqual(repr(r1.eq(s1)), "(eq (cat (sig r1__a)) (sig s1))")


class ConnectTestCase(FHDLTestCase):
    def setUp_flat(self):
        self.core_layout = [
            ("addr",   32, DIR_FANOUT),
            ("data_r", 32, DIR_FANIN),
            ("data_w", 32, DIR_FANIN),
        ]
        self.periph_layout = [
            ("addr",   32, DIR_FANOUT),
            ("data_r", 32, DIR_FANIN),
            ("data_w", 32, DIR_FANIN),
        ]

    def setUp_nested(self):
        self.core_layout = [
            ("addr",   32, DIR_FANOUT),
            ("data", [
                ("r",  32, DIR_FANIN),
                ("w",  32, DIR_FANIN),
            ]),
        ]
        self.periph_layout = [
            ("addr",   32, DIR_FANOUT),
            ("data", [
                ("r",  32, DIR_FANIN),
                ("w",  32, DIR_FANIN),
            ]),
        ]

    def test_flat(self):
        self.setUp_flat()

        core    = Record(self.core_layout)
        periph1 = Record(self.periph_layout)
        periph2 = Record(self.periph_layout)

        stmts = core.connect(periph1, periph2)
        self.assertRepr(stmts, """(
            (eq (sig periph1__addr) (sig core__addr))
            (eq (sig periph2__addr) (sig core__addr))
            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
        )""")

    def test_flat_include(self):
        self.setUp_flat()

        core    = Record(self.core_layout)
        periph1 = Record(self.periph_layout)
        periph2 = Record(self.periph_layout)

        stmts = core.connect(periph1, periph2, include={"addr": True})
        self.assertRepr(stmts, """(
            (eq (sig periph1__addr) (sig core__addr))
            (eq (sig periph2__addr) (sig core__addr))
        )""")

    def test_flat_exclude(self):
        self.setUp_flat()

        core    = Record(self.core_layout)
        periph1 = Record(self.periph_layout)
        periph2 = Record(self.periph_layout)

        stmts = core.connect(periph1, periph2, exclude={"addr": True})
        self.assertRepr(stmts, """(
            (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r)))
            (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w)))
        )""")

    def test_nested(self):
        self.setUp_nested()

        core    = Record(self.core_layout)
        periph1 = Record(self.periph_layout)
        periph2 = Record(self.periph_layout)

        stmts = core.connect(periph1, periph2)
        self.maxDiff = None
        self.assertRepr(stmts, """(
            (eq (sig periph1__addr) (sig core__addr))
            (eq (sig periph2__addr) (sig core__addr))
            (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r)))
            (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w)))
        )""")

    def test_wrong_include_exclude(self):
        self.setUp_flat()

        core   = Record(self.core_layout)
        periph = Record(self.periph_layout)

        with self.assertRaisesRegex(AttributeError,
                r"^Cannot include field 'foo' because it is not present in record 'core'$"):
            core.connect(periph, include={"foo": True})

        with self.assertRaisesRegex(AttributeError,
                r"^Cannot exclude field 'foo' because it is not present in record 'core'$"):
            core.connect(periph, exclude={"foo": True})

    def test_wrong_direction(self):
        recs = [Record([("x", 1)]) for _ in range(2)]

        with self.assertRaisesRegex(TypeError,
                (r"^Cannot connect field 'x' of unnamed record because it does not have "
                    r"a direction$")):
            recs[0].connect(recs[1])

    def test_wrong_missing_field(self):
        core   = Record([("addr", 32, DIR_FANOUT)])
        periph = Record([])

        with self.assertRaisesRegex(AttributeError,
                (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' "
                    r"because the subordinate record does not have this field$")):
            core.connect(periph)


# tests/test_hdl_xfrm.py

# amaranth: UnusedElaboratable=no

import warnings

from amaranth.hdl.ast import *
from amaranth.hdl.cd import *
from amaranth.hdl.ir import *
from amaranth.hdl.xfrm import *
from amaranth.hdl.mem import *

from .utils import *
from amaranth._utils import _ignore_deprecated


class DomainRenamerTestCase(FHDLTestCase):
    def setUp(self):
        self.s1 = Signal()
        self.s2 = Signal()
        self.s3 = Signal()
        self.s4 = Signal()
        self.s5 = Signal()
        self.c1 = Signal()

    def test_rename_signals(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(ClockSignal()),
            ResetSignal().eq(self.s2),
            self.s3.eq(0),
            self.s4.eq(ClockSignal("other")),
            self.s5.eq(ResetSignal("other")),
        )
        f.add_driver(self.s1, None)
        f.add_driver(self.s2, None)
        f.add_driver(self.s3, "sync")

        f = DomainRenamer("pix")(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (clk pix))
            (eq (rst pix) (sig s2))
            (eq (sig s3) (const 1'd0))
            (eq (sig s4) (clk other))
            (eq (sig s5) (rst other))
        )
        """)
        self.assertEqual(f.drivers, {
            None: SignalSet((self.s1, self.s2)),
            "pix": SignalSet((self.s3,)),
        })

    def test_rename_multi(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(ClockSignal()),
            self.s2.eq(ResetSignal("other")),
        )

        f = DomainRenamer({"sync": "pix", "other": "pix2"})(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (clk pix))
            (eq (sig s2) (rst pix2))
        )
        """)

    def test_rename_cd(self):
        cd_sync = ClockDomain()
        cd_pix  = ClockDomain()

        f = Fragment()
        f.add_domains(cd_sync, cd_pix)

        f = DomainRenamer("ext")(f)
        self.assertEqual(cd_sync.name, "ext")
        self.assertEqual(f.domains, {
            "ext": cd_sync,
            "pix": cd_pix,
        })

    def test_rename_cd_preserves_allow_reset_less(self):
        cd_pix  = ClockDomain(reset_less=True)

        f = Fragment()
        f.add_domains(cd_pix)
        f.add_statements(
            self.s1.eq(ResetSignal(allow_reset_less=True)),
        )

        f = DomainRenamer("pix")(f)
        f = DomainLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd0))
        )
        """)


    def test_rename_cd_subfragment(self):
        cd_sync = ClockDomain()
        cd_pix  = ClockDomain()

        f1 = Fragment()
        f1.add_domains(cd_sync, cd_pix)
        f2 = Fragment()
        f2.add_domains(cd_sync)
        f1.add_subfragment(f2)

        f1 = DomainRenamer("ext")(f1)
        self.assertEqual(cd_sync.name, "ext")
        self.assertEqual(f1.domains, {
            "ext": cd_sync,
            "pix": cd_pix,
        })

    def test_rename_wrong_to_comb(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain 'sync' may not be renamed to 'comb'$"):
            DomainRenamer("comb")

    def test_rename_wrong_from_comb(self):
        with self.assertRaisesRegex(ValueError,
                r"^Domain 'comb' may not be renamed$"):
            DomainRenamer({"comb": "sync"})


class DomainLowererTestCase(FHDLTestCase):
    def setUp(self):
        self.s = Signal()

    def test_lower_clk(self):
        sync = ClockDomain()
        f = Fragment()
        f.add_domains(sync)
        f.add_statements(
            self.s.eq(ClockSignal("sync"))
        )

        f = DomainLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s) (sig clk))
        )
        """)

    def test_lower_rst(self):
        sync = ClockDomain()
        f = Fragment()
        f.add_domains(sync)
        f.add_statements(
            self.s.eq(ResetSignal("sync"))
        )

        f = DomainLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s) (sig rst))
        )
        """)

    def test_lower_rst_reset_less(self):
        sync = ClockDomain(reset_less=True)
        f = Fragment()
        f.add_domains(sync)
        f.add_statements(
            self.s.eq(ResetSignal("sync", allow_reset_less=True))
        )

        f = DomainLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s) (const 1'd0))
        )
        """)

    def test_lower_drivers(self):
        sync = ClockDomain()
        pix = ClockDomain()
        f = Fragment()
        f.add_domains(sync, pix)
        f.add_driver(ClockSignal("pix"), None)
        f.add_driver(ResetSignal("pix"), "sync")

        f = DomainLowerer()(f)
        self.assertEqual(f.drivers, {
            None: SignalSet((pix.clk,)),
            "sync": SignalSet((pix.rst,))
        })

    def test_lower_wrong_domain(self):
        f = Fragment()
        f.add_statements(
            self.s.eq(ClockSignal("xxx"))
        )

        with self.assertRaisesRegex(DomainError,
                r"^Signal \(clk xxx\) refers to nonexistent domain 'xxx'$"):
            DomainLowerer()(f)

    def test_lower_wrong_reset_less_domain(self):
        sync = ClockDomain(reset_less=True)
        f = Fragment()
        f.add_domains(sync)
        f.add_statements(
            self.s.eq(ResetSignal("sync"))
        )

        with self.assertRaisesRegex(DomainError,
                r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"):
            DomainLowerer()(f)


class SampleLowererTestCase(FHDLTestCase):
    def setUp(self):
        self.i = Signal()
        self.o1 = Signal()
        self.o2 = Signal()
        self.o3 = Signal()

    @_ignore_deprecated
    def test_lower_signal(self):
        f = Fragment()
        f.add_statements(
            self.o1.eq(Sample(self.i, 2, "sync")),
            self.o2.eq(Sample(self.i, 1, "sync")),
            self.o3.eq(Sample(self.i, 1, "pix")),
        )

        f = SampleLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig o1) (sig $sample$s$i$sync$2))
            (eq (sig o2) (sig $sample$s$i$sync$1))
            (eq (sig o3) (sig $sample$s$i$pix$1))
            (eq (sig $sample$s$i$sync$1) (sig i))
            (eq (sig $sample$s$i$sync$2) (sig $sample$s$i$sync$1))
            (eq (sig $sample$s$i$pix$1) (sig i))
        )
        """)
        self.assertEqual(len(f.drivers["sync"]), 2)
        self.assertEqual(len(f.drivers["pix"]), 1)

    @_ignore_deprecated
    def test_lower_const(self):
        f = Fragment()
        f.add_statements(
            self.o1.eq(Sample(1, 2, "sync")),
        )

        f = SampleLowerer()(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig o1) (sig $sample$c$1$sync$2))
            (eq (sig $sample$c$1$sync$1) (const 1'd1))
            (eq (sig $sample$c$1$sync$2) (sig $sample$c$1$sync$1))
        )
        """)
        self.assertEqual(len(f.drivers["sync"]), 2)


class SwitchCleanerTestCase(FHDLTestCase):
    def test_clean(self):
        a = Signal()
        b = Signal()
        c = Signal()
        stmts = [
            Switch(a, {
                1: a.eq(0),
                0: [
                    b.eq(1),
                    Switch(b, {1: [
                        Switch(a|b, {})
                    ]})
                ]
            })
        ]

        self.assertRepr(SwitchCleaner()(stmts), """
        (
            (switch (sig a)
                (case 1
                    (eq (sig a) (const 1'd0)))
                (case 0
                    (eq (sig b) (const 1'd1)))
            )
        )
        """)


class LHSGroupAnalyzerTestCase(FHDLTestCase):
    def test_no_group_unrelated(self):
        a = Signal()
        b = Signal()
        stmts = [
            a.eq(0),
            b.eq(0),
        ]

        groups = LHSGroupAnalyzer()(stmts)
        self.assertEqual(list(groups.values()), [
            SignalSet((a,)),
            SignalSet((b,)),
        ])

    def test_group_related(self):
        a = Signal()
        b = Signal()
        stmts = [
            a.eq(0),
            Cat(a, b).eq(0),
        ]

        groups = LHSGroupAnalyzer()(stmts)
        self.assertEqual(list(groups.values()), [
            SignalSet((a, b)),
        ])

    def test_no_loops(self):
        a = Signal()
        b = Signal()
        stmts = [
            a.eq(0),
            Cat(a, b).eq(0),
            Cat(a, b).eq(0),
        ]

        groups = LHSGroupAnalyzer()(stmts)
        self.assertEqual(list(groups.values()), [
            SignalSet((a, b)),
        ])

    def test_switch(self):
        a = Signal()
        b = Signal()
        stmts = [
            a.eq(0),
            Switch(a, {
                1: b.eq(0),
            })
        ]

        groups = LHSGroupAnalyzer()(stmts)
        self.assertEqual(list(groups.values()), [
            SignalSet((a,)),
            SignalSet((b,)),
        ])

    def test_lhs_empty(self):
        stmts = [
            Cat().eq(0)
        ]

        groups = LHSGroupAnalyzer()(stmts)
        self.assertEqual(list(groups.values()), [
        ])


class LHSGroupFilterTestCase(FHDLTestCase):
    def test_filter(self):
        a = Signal()
        b = Signal()
        c = Signal()
        stmts = [
            Switch(a, {
                1: a.eq(0),
                0: [
                    b.eq(1),
                    Switch(b, {1: []})
                ]
            })
        ]

        self.assertRepr(LHSGroupFilter(SignalSet((a,)))(stmts), """
        (
            (switch (sig a)
                (case 1
                    (eq (sig a) (const 1'd0)))
                (case 0 )
            )
        )
        """)

    def test_lhs_empty(self):
        stmts = [
            Cat().eq(0)
        ]

        self.assertRepr(LHSGroupFilter(SignalSet())(stmts), "()")


class ResetInserterTestCase(FHDLTestCase):
    def setUp(self):
        self.s1 = Signal()
        self.s2 = Signal(reset=1)
        self.s3 = Signal(reset=1, reset_less=True)
        self.c1 = Signal()

    def test_reset_default(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(1)
        )
        f.add_driver(self.s1, "sync")

        f = ResetInserter(self.c1)(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (switch (sig c1)
                (case 1 (eq (sig s1) (const 1'd0)))
            )
        )
        """)

    def test_reset_cd(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(1),
            self.s2.eq(0),
        )
        f.add_domains(ClockDomain("sync"))
        f.add_driver(self.s1, "sync")
        f.add_driver(self.s2, "pix")

        f = ResetInserter({"pix": self.c1})(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (eq (sig s2) (const 1'd0))
            (switch (sig c1)
                (case 1 (eq (sig s2) (const 1'd1)))
            )
        )
        """)

    def test_reset_value(self):
        f = Fragment()
        f.add_statements(
            self.s2.eq(0)
        )
        f.add_driver(self.s2, "sync")

        f = ResetInserter(self.c1)(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s2) (const 1'd0))
            (switch (sig c1)
                (case 1 (eq (sig s2) (const 1'd1)))
            )
        )
        """)

    def test_reset_less(self):
        f = Fragment()
        f.add_statements(
            self.s3.eq(0)
        )
        f.add_driver(self.s3, "sync")

        f = ResetInserter(self.c1)(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s3) (const 1'd0))
            (switch (sig c1)
                (case 1 )
            )
        )
        """)


class EnableInserterTestCase(FHDLTestCase):
    def setUp(self):
        self.s1 = Signal()
        self.s2 = Signal()
        self.s3 = Signal()
        self.c1 = Signal()

    def test_enable_default(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(1)
        )
        f.add_driver(self.s1, "sync")

        f = EnableInserter(self.c1)(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (switch (sig c1)
                (case 0 (eq (sig s1) (sig s1)))
            )
        )
        """)

    def test_enable_cd(self):
        f = Fragment()
        f.add_statements(
            self.s1.eq(1),
            self.s2.eq(0),
        )
        f.add_driver(self.s1, "sync")
        f.add_driver(self.s2, "pix")

        f = EnableInserter({"pix": self.c1})(f)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (eq (sig s2) (const 1'd0))
            (switch (sig c1)
                (case 0 (eq (sig s2) (sig s2)))
            )
        )
        """)

    def test_enable_subfragment(self):
        f1 = Fragment()
        f1.add_statements(
            self.s1.eq(1)
        )
        f1.add_driver(self.s1, "sync")

        f2 = Fragment()
        f2.add_statements(
            self.s2.eq(1)
        )
        f2.add_driver(self.s2, "sync")
        f1.add_subfragment(f2)

        f1 = EnableInserter(self.c1)(f1)
        (f2, _), = f1.subfragments
        self.assertRepr(f1.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (switch (sig c1)
                (case 0 (eq (sig s1) (sig s1)))
            )
        )
        """)
        self.assertRepr(f2.statements, """
        (
            (eq (sig s2) (const 1'd1))
            (switch (sig c1)
                (case 0 (eq (sig s2) (sig s2)))
            )
        )
        """)

    def test_enable_read_port(self):
        mem = Memory(width=8, depth=4)
        f = EnableInserter(self.c1)(mem.read_port(transparent=False)).elaborate(platform=None)
        self.assertRepr(f.named_ports["EN"][0], """
        (m (sig c1) (sig mem_r_en) (const 1'd0))
        """)

    def test_enable_write_port(self):
        mem = Memory(width=8, depth=4)
        f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None)
        self.assertRepr(f.named_ports["EN"][0], """
        (m
            (sig c1)
            (cat
                (cat
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                    (slice (sig mem_w_en) 0:1)
                )
            )
            (const 8'd0)
        )
        """)


class _MockElaboratable(Elaboratable):
    def __init__(self):
        self.s1 = Signal()

    def elaborate(self, platform):
        f = Fragment()
        f.add_statements(
            self.s1.eq(1)
        )
        f.add_driver(self.s1, "sync")
        return f


class TransformedElaboratableTestCase(FHDLTestCase):
    def setUp(self):
        self.c1 = Signal()
        self.c2 = Signal()

    def test_getattr(self):
        e = _MockElaboratable()
        te = EnableInserter(self.c1)(e)

        self.assertIs(te.s1, e.s1)

    def test_composition(self):
        e = _MockElaboratable()
        te1 = EnableInserter(self.c1)(e)
        te2 = ResetInserter(self.c2)(te1)

        self.assertIsInstance(te1, TransformedElaboratable)
        self.assertIs(te1, te2)

        f = Fragment.get(te2, None)
        self.assertRepr(f.statements, """
        (
            (eq (sig s1) (const 1'd1))
            (switch (sig c1)
                (case 0 (eq (sig s1) (sig s1)))
            )
            (switch (sig c2)
                (case 1 (eq (sig s1) (const 1'd0)))
            )
        )
        """)


# tests/test_lib_cdc.py

# amaranth: UnusedElaboratable=no

from amaranth.hdl import *
from amaranth.sim import *
from amaranth.lib.cdc import *

from .utils import *


class FFSynchronizerTestCase(FHDLTestCase):
    def test_stages_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Synchronization stage count must be a positive integer, not 0$"):
            FFSynchronizer(Signal(), Signal(), stages=0)
        with self.assertRaisesRegex(ValueError,
                r"^Synchronization stage count may not safely be less than 2$"):
            FFSynchronizer(Signal(), Signal(), stages=1)

    def test_basic(self):
        i = Signal()
        o = Signal()
        frag = FFSynchronizer(i, o)

        sim = Simulator(frag)
        sim.add_clock(1e-6)
        def process():
            self.assertEqual((yield o), 0)
            yield i.eq(1)
            yield Tick()
            self.assertEqual((yield o), 0)
            yield Tick()
            self.assertEqual((yield o), 0)
            yield Tick()
            self.assertEqual((yield o), 1)
        sim.add_process(process)
        sim.run()

    def test_reset_value(self):
        i = Signal(reset=1)
        o = Signal()
        frag = FFSynchronizer(i, o, reset=1)

        sim = Simulator(frag)
        sim.add_clock(1e-6)
        def process():
            self.assertEqual((yield o), 1)
            yield i.eq(0)
            yield Tick()
            self.assertEqual((yield o), 1)
            yield Tick()
            self.assertEqual((yield o), 1)
            yield Tick()
            self.assertEqual((yield o), 0)
        sim.add_process(process)
        sim.run()


class AsyncFFSynchronizerTestCase(FHDLTestCase):
    def test_stages_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Synchronization stage count must be a positive integer, not 0$"):
            ResetSynchronizer(Signal(), stages=0)
        with self.assertRaisesRegex(ValueError,
                r"^Synchronization stage count may not safely be less than 2$"):
            ResetSynchronizer(Signal(), stages=1)

    def test_edge_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"):
            AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx")

    def test_width_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^AsyncFFSynchronizer input width must be 1, not 2$"):
            AsyncFFSynchronizer(Signal(2), Signal(), o_domain="sync")
        with self.assertRaisesRegex(ValueError,
                r"^AsyncFFSynchronizer output width must be 1, not 2$"):
            AsyncFFSynchronizer(Signal(), Signal(2), o_domain="sync")

    def test_pos_edge(self):
        i = Signal()
        o = Signal()
        m = Module()
        m.domains += ClockDomain("sync")
        m.submodules += AsyncFFSynchronizer(i, o)

        sim = Simulator(m)
        sim.add_clock(1e-6)
        def process():
            # initial reset
            self.assertEqual((yield i), 0)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)

            yield i.eq(1)
            yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield i.eq(0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
        sim.add_process(process)
        with sim.write_vcd("test.vcd"):
            sim.run()

    def test_neg_edge(self):
        i = Signal(reset=1)
        o = Signal()
        m = Module()
        m.domains += ClockDomain("sync")
        m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg")

        sim = Simulator(m)
        sim.add_clock(1e-6)
        def process():
            # initial reset
            self.assertEqual((yield i), 1)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)

            yield i.eq(0)
            yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield i.eq(1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield o), 0)
            yield Tick(); yield Delay(1e-8)
        sim.add_process(process)
        with sim.write_vcd("test.vcd"):
            sim.run()


class ResetSynchronizerTestCase(FHDLTestCase):
    def test_stages_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Synchronization stage count must be a positive integer, not 0$"):
            ResetSynchronizer(Signal(), stages=0)
        with self.assertRaisesRegex(ValueError,
                r"^Synchronization stage count may not safely be less than 2$"):
            ResetSynchronizer(Signal(), stages=1)

    def test_basic(self):
        arst = Signal()
        m = Module()
        m.domains += ClockDomain("sync")
        m.submodules += ResetSynchronizer(arst)
        s = Signal(reset=1)
        m.d.sync += s.eq(0)

        sim = Simulator(m)
        sim.add_clock(1e-6)
        def process():
            # initial reset
            self.assertEqual((yield s), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 0)
            yield Tick(); yield Delay(1e-8)

            yield arst.eq(1)
            yield Delay(1e-8)
            self.assertEqual((yield s), 0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 1)
            yield arst.eq(0)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 1)
            yield Tick(); yield Delay(1e-8)
            self.assertEqual((yield s), 0)
            yield Tick(); yield Delay(1e-8)
        sim.add_process(process)
        with sim.write_vcd("test.vcd"):
            sim.run()


# TODO: test with distinct clocks
class PulseSynchronizerTestCase(FHDLTestCase):
    def test_stages_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Synchronization stage count must be a positive integer, not 0$"):
            PulseSynchronizer("w", "r", stages=0)
        with self.assertRaisesRegex(ValueError,
                r"^Synchronization stage count may not safely be less than 2$"):
            PulseSynchronizer("w", "r", stages=1)

    def test_smoke(self):
        m = Module()
        m.domains += ClockDomain("sync")
        ps = m.submodules.dut = PulseSynchronizer("sync", "sync")

        sim = Simulator(m)
        sim.add_clock(1e-6)
        def process():
            yield ps.i.eq(0)
            # TODO: think about reset
            for n in range(5):
                yield Tick()
            # Make sure no pulses are generated in quiescent state
            for n in range(3):
                yield Tick()
                self.assertEqual((yield ps.o), 0)
            # Check conservation of pulses
            accum = 0
            for n in range(10):
                yield ps.i.eq(1 if n < 4 else 0)
                yield Tick()
                accum += yield ps.o
            self.assertEqual(accum, 4)
        sim.add_process(process)
        sim.run()


# tests/test_lib_coding.py

from amaranth.hdl import *
from amaranth.asserts import *
from amaranth.sim import *
from amaranth.lib.coding import *

from .utils import *


class EncoderTestCase(FHDLTestCase):
    def test_basic(self):
        enc = Encoder(4)
        def process():
            self.assertEqual((yield enc.n), 1)
            self.assertEqual((yield enc.o), 0)

            yield enc.i.eq(0b0001)
            yield Settle()
            self.assertEqual((yield enc.n), 0)
            self.assertEqual((yield enc.o), 0)

            yield enc.i.eq(0b0100)
            yield Settle()
            self.assertEqual((yield enc.n), 0)
            self.assertEqual((yield enc.o), 2)

            yield enc.i.eq(0b0110)
            yield Settle()
            self.assertEqual((yield enc.n), 1)
            self.assertEqual((yield enc.o), 0)

        sim = Simulator(enc)
        sim.add_process(process)
        sim.run()


class PriorityEncoderTestCase(FHDLTestCase):
    def test_basic(self):
        enc = PriorityEncoder(4)
        def process():
            self.assertEqual((yield enc.n), 1)
            self.assertEqual((yield enc.o), 0)

            yield enc.i.eq(0b0001)
            yield Settle()
            self.assertEqual((yield enc.n), 0)
            self.assertEqual((yield enc.o), 0)

            yield enc.i.eq(0b0100)
            yield Settle()
            self.assertEqual((yield enc.n), 0)
            self.assertEqual((yield enc.o), 2)

            yield enc.i.eq(0b0110)
            yield Settle()
            self.assertEqual((yield enc.n), 0)
            self.assertEqual((yield enc.o), 1)

        sim = Simulator(enc)
        sim.add_process(process)
        sim.run()


class DecoderTestCase(FHDLTestCase):
    def test_basic(self):
        dec = Decoder(4)
        def process():
            self.assertEqual((yield dec.o), 0b0001)

            yield dec.i.eq(1)
            yield Settle()
            self.assertEqual((yield dec.o), 0b0010)

            yield dec.i.eq(3)
            yield Settle()
            self.assertEqual((yield dec.o), 0b1000)

            yield dec.n.eq(1)
            yield Settle()
            self.assertEqual((yield dec.o), 0b0000)

        sim = Simulator(dec)
        sim.add_process(process)
        sim.run()


class ReversibleSpec(Elaboratable):
    def __init__(self, encoder_cls, decoder_cls, args):
        self.encoder_cls = encoder_cls
        self.decoder_cls = decoder_cls
        self.coder_args  = args

    def elaborate(self, platform):
        m = Module()
        enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args)
        m.submodules += enc, dec
        m.d.comb += [
            dec.i.eq(enc.o),
            Assert(enc.i == dec.o)
        ]
        return m


class HammingDistanceSpec(Elaboratable):
    def __init__(self, distance, encoder_cls, args):
        self.distance    = distance
        self.encoder_cls = encoder_cls
        self.coder_args  = args

    def elaborate(self, platform):
        m = Module()
        enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args)
        m.submodules += enc1, enc2
        m.d.comb += [
            Assume(enc1.i + 1 == enc2.i),
            Assert(sum(enc1.o ^ enc2.o) == self.distance)
        ]
        return m


class GrayCoderTestCase(FHDLTestCase):
    def test_reversible(self):
        spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,))
        self.assertFormal(spec, mode="prove")

    def test_distance(self):
        spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,))
        self.assertFormal(spec, mode="prove")


# tests/test_lib_crc.py

# amaranth: UnusedElaboratable=no

import unittest

from amaranth.sim import *
from amaranth.lib.crc import Algorithm, Processor, catalog


# Subset of catalogue CRCs used to test the hardware CRC implementation,
# as testing all algorithms takes a long time for little benefit.
# Selected to ensure coverage of CRC width, initial value, reflection, and
# XOR output.
CRCS = [
    "CRC3_GSM", "CRC4_INTERLAKEN", "CRC5_USB", "CRC8_AUTOSAR", "CRC8_BLUETOOTH",
    "CRC8_I_432_1", "CRC12_UMTS", "CRC15_CAN", "CRC16_ARC", "CRC16_IBM_3740",
    "CRC17_CAN_FD", "CRC21_CAN_FD", "CRC24_FLEXRAY_A", "CRC32_AUTOSAR",
    "CRC32_BZIP2", "CRC32_ISO_HDLC", "CRC40_GSM"
]

# All catalogue CRCs with their associated check values and residues from
# the reveng catalogue. Used to verify our computation of the check and
# residue values.
CRC_CHECKS = {
    "CRC3_GSM": (0x4, 0x2),
    "CRC3_ROHC": (0x6, 0x0),
    "CRC4_G_704": (0x7, 0x0),
    "CRC4_ITU": (0x7, 0x0),
    "CRC4_INTERLAKEN": (0xb, 0x2),
    "CRC5_EPC_C1G2": (0x00, 0x00),
    "CRC5_EPC": (0x00, 0x00),
    "CRC5_G_704": (0x07, 0x00),
    "CRC5_ITU": (0x07, 0x00),
    "CRC5_USB": (0x19, 0x06),
    "CRC6_CDMA2000_A": (0x0d, 0x00),
    "CRC6_CDMA2000_B": (0x3b, 0x00),
    "CRC6_DARC": (0x26, 0x00),
    "CRC6_G_704": (0x06, 0x00),
    "CRC6_ITU": (0x06, 0x00),
    "CRC6_GSM": (0x13, 0x3a),
    "CRC7_MMC": (0x75, 0x00),
    "CRC7_ROHC": (0x53, 0x00),
    "CRC7_UMTS": (0x61, 0x00),
    "CRC8_AUTOSAR": (0xdf, 0x42),
    "CRC8_BLUETOOTH": (0x26, 0x00),
    "CRC8_CDMA2000": (0xda, 0x00),
    "CRC8_DARC": (0x15, 0x00),
    "CRC8_DVB_S2": (0xbc, 0x00),
    "CRC8_GSM_A": (0x37, 0x00),
    "CRC8_GSM_B": (0x94, 0x53),
    "CRC8_HITAG": (0xb4, 0x00),
    "CRC8_I_432_1": (0xa1, 0xac),
    "CRC8_ITU": (0xa1, 0xac),
    "CRC8_I_CODE": (0x7e, 0x00),
    "CRC8_LTE": (0xea, 0x00),
    "CRC8_MAXIM_DOW": (0xa1, 0x00),
    "CRC8_MAXIM": (0xa1, 0x00),
    "CRC8_MIFARE_MAD": (0x99, 0x00),
    "CRC8_NRSC_5": (0xf7, 0x00),
    "CRC8_OPENSAFETY": (0x3e, 0x00),
    "CRC8_ROHC": (0xd0, 0x00),
    "CRC8_SAE_J1850": (0x4b, 0xc4),
    "CRC8_SMBUS": (0xf4, 0x00),
    "CRC8_TECH_3250": (0x97, 0x00),
    "CRC8_AES": (0x97, 0x00),
    "CRC8_ETU": (0x97, 0x00),
    "CRC8_WCDMA": (0x25, 0x00),
    "CRC10_ATM": (0x199, 0x000),
    "CRC10_I_610": (0x199, 0x000),
    "CRC10_CDMA2000": (0x233, 0x000),
    "CRC10_GSM": (0x12a, 0x0c6),
    "CRC11_FLEXRAY": (0x5a3, 0x000),
    "CRC11_UMTS": (0x061, 0x000),
    "CRC12_CDMA2000": (0xd4d, 0x000),
    "CRC12_DECT": (0xf5b, 0x000),
    "CRC12_GSM": (0xb34, 0x178),
    "CRC12_UMTS": (0xdaf, 0x000),
    "CRC12_3GPP": (0xdaf, 0x000),
    "CRC13_BBC": (0x04fa, 0x0000),
    "CRC14_DARC": (0x082d, 0x0000),
    "CRC14_GSM": (0x30ae, 0x031e),
    "CRC15_CAN": (0x059e, 0x0000),
    "CRC15_MPT1327": (0x2566, 0x6815),
    "CRC16_ARC": (0xbb3d, 0x0000),
    "CRC16_IBM": (0xbb3d, 0x0000),
    "CRC16_CDMA2000": (0x4c06, 0x0000),
    "CRC16_CMS": (0xaee7, 0x0000),
    "CRC16_DDS_110": (0x9ecf, 0x0000),
    "CRC16_DECT_R": (0x007e, 0x0589),
    "CRC16_DECT_X": (0x007f, 0x0000),
    "CRC16_DNP": (0xea82, 0x66c5),
    "CRC16_EN_13757": (0xc2b7, 0xa366),
    "CRC16_GENIBUS": (0xd64e, 0x1d0f),
    "CRC16_DARC": (0xd64e, 0x1d0f),
    "CRC16_EPC": (0xd64e, 0x1d0f),
    "CRC16_EPC_C1G2": (0xd64e, 0x1d0f),
    "CRC16_I_CODE": (0xd64e, 0x1d0f),
    "CRC16_GSM": (0xce3c, 0x1d0f),
    "CRC16_IBM_3740": (0x29b1, 0x0000),
    "CRC16_AUTOSAR": (0x29b1, 0x0000),
    "CRC16_CCITT_FALSE": (0x29b1, 0x0000),
    "CRC16_IBM_SDLC": (0x906e, 0xf0b8),
    "CRC16_ISO_HDLC": (0x906e, 0xf0b8),
    "CRC16_ISO_IEC_14443_3_B": (0x906e, 0xf0b8),
    "CRC16_X25": (0x906e, 0xf0b8),
    "CRC16_ISO_IEC_14443_3_A": (0xbf05, 0x0000),
    "CRC16_KERMIT": (0x2189, 0x0000),
    "CRC16_BLUETOOTH": (0x2189, 0x0000),
    "CRC16_CCITT": (0x2189, 0x0000),
    "CRC16_CCITT_TRUE": (0x2189, 0x0000),
    "CRC16_V_41_LSB": (0x2189, 0x0000),
    "CRC16_LJ1200": (0xbdf4, 0x0000),
    "CRC16_M17": (0x772b, 0x0000),
    "CRC16_MAXIM_DOW": (0x44c2, 0xb001),
    "CRC16_MAXIM": (0x44c2, 0xb001),
    "CRC16_MCRF4XX": (0x6f91, 0x0000),
    "CRC16_MODBUS": (0x4b37, 0x0000),
    "CRC16_NRSC_5": (0xa066, 0x0000),
    "CRC16_OPENSAFETY_A": (0x5d38, 0x0000),
    "CRC16_OPENSAFETY_B": (0x20fe, 0x0000),
    "CRC16_PROFIBUS": (0xa819, 0xe394),
    "CRC16_IEC_61158_2": (0xa819, 0xe394),
    "CRC16_RIELLO": (0x63d0, 0x0000),
    "CRC16_SPI_FUJITSU": (0xe5cc, 0x0000),
    "CRC16_AUG_CCITT": (0xe5cc, 0x0000),
    "CRC16_T10_DIF": (0xd0db, 0x0000),
    "CRC16_TELEDISK": (0x0fb3, 0x0000),
    "CRC16_TMS37157": (0x26b1, 0x0000),
    "CRC16_UMTS": (0xfee8, 0x0000),
    "CRC16_BUYPASS": (0xfee8, 0x0000),
    "CRC16_VERIFONE": (0xfee8, 0x0000),
    "CRC16_USB": (0xb4c8, 0xb001),
    "CRC16_XMODEM": (0x31c3, 0x0000),
    "CRC16_ACORN": (0x31c3, 0x0000),
    "CRC16_LTE": (0x31c3, 0x0000),
    "CRC16_V_41_MSB": (0x31c3, 0x0000),
    "CRC16_ZMODEM": (0x31c3, 0x0000),
    "CRC17_CAN_FD": (0x04f03, 0x00000),
    "CRC21_CAN_FD": (0x0ed841, 0x000000),
    "CRC24_BLE": (0xc25a56, 0x000000),
    "CRC24_FLEXRAY_A": (0x7979bd, 0x000000),
    "CRC24_FLEXRAY_B": (0x1f23b8, 0x000000),
    "CRC24_INTERLAKEN": (0xb4f3e6, 0x144e63),
    "CRC24_LTE_A": (0xcde703, 0x000000),
    "CRC24_LTE_B": (0x23ef52, 0x000000),
    "CRC24_OPENPGP": (0x21cf02, 0x000000),
    "CRC24_OS_9": (0x200fa5, 0x800fe3),
    "CRC30_CDMA": (0x04c34abf, 0x34efa55a),
    "CRC31_PHILIPS": (0x0ce9e46c, 0x4eaf26f1),
    "CRC32_AIXM": (0x3010bf7f, 0x00000000),
    "CRC32_AUTOSAR": (0x1697d06a, 0x904cddbf),
    "CRC32_BASE91_D": (0x87315576, 0x45270551),
    "CRC32_BZIP2": (0xfc891918, 0xc704dd7b),
    "CRC32_AAL5": (0xfc891918, 0xc704dd7b),
    "CRC32_DECT_B": (0xfc891918, 0xc704dd7b),
    "CRC32_CD_ROM_EDC": (0x6ec2edc4, 0x00000000),
    "CRC32_CKSUM": (0x765e7680, 0xc704dd7b),
    "CRC32_POSIX": (0x765e7680, 0xc704dd7b),
    "CRC32_ISCSI": (0xe3069283, 0xb798b438),
    "CRC32_BASE91_C": (0xe3069283, 0xb798b438),
    "CRC32_CASTAGNOLI": (0xe3069283, 0xb798b438),
    "CRC32_INTERLAKEN": (0xe3069283, 0xb798b438),
    "CRC32_ISO_HDLC": (0xcbf43926, 0xdebb20e3),
    "CRC32_ADCCP": (0xcbf43926, 0xdebb20e3),
    "CRC32_V_42": (0xcbf43926, 0xdebb20e3),
    "CRC32_XZ": (0xcbf43926, 0xdebb20e3),
    "CRC32_PKZIP": (0xcbf43926, 0xdebb20e3),
    "CRC32_ETHERNET": (0xcbf43926, 0xdebb20e3),
    "CRC32_JAMCRC": (0x340bc6d9, 0x00000000),
    "CRC32_MEF": (0xd2c22f51, 0x00000000),
    "CRC32_MPEG_2": (0x0376e6e7, 0x00000000),
    "CRC32_XFER": (0xbd0be338, 0x00000000),
    "CRC40_GSM": (0xd4164fc646, 0xc4ff8071ff),
    "CRC64_ECMA_182": (0x6c40df5f0b497347, 0x0000000000000000),
    "CRC64_GO_ISO": (0xb90956c775a41001, 0x5300000000000000),
    "CRC64_MS": (0x75d4b74f024eceea, 0x0000000000000000),
    "CRC64_REDIS": (0xe9c6d914c4b8d9ca, 0x0000000000000000),
    "CRC64_WE": (0x62ec59e3f1a4f00a, 0xfcacbebd5931a992),
    "CRC64_XZ": (0x995dc9bbdf1939fa, 0x49958c9abd7d353f),
    "CRC64_ECMA": (0x995dc9bbdf1939fa, 0x49958c9abd7d353f),
    "CRC82_DARC": (0x09ea83f625023801fd612, 0x000000000000000000000),
}


class CRCTestCase(unittest.TestCase):
    def test_checks(self):
        """
        Verify computed check values and residues match catalogue entries.
        """
        for name in dir(catalog):
            if name.startswith("CRC"):
                crc = getattr(catalog, name)(data_width=8)
                check, residue = CRC_CHECKS[name]
                assert crc.compute(b"123456789") == check
                assert crc.residue() == residue

    def test_repr(self):
        algorithm = catalog.CRC8_AUTOSAR
        assert repr(algorithm) == "Algorithm(crc_width=8, polynomial=0x2f," \
            " initial_crc=0xff, reflect_input=False, reflect_output=False," \
            " xor_output=0xff)"

        params = algorithm(data_width=8)
        assert repr(params) == "Parameters(Algorithm(crc_width=8," \
            " polynomial=0x2f, initial_crc=0xff, reflect_input=False," \
            " reflect_output=False, xor_output=0xff), data_width=8)"

    def test_processor_typecheck(self):
        with self.assertRaises(TypeError):
            proc = Processor(12)

    def test_algorithm_range_checks(self):
        with self.assertRaises(ValueError):
            Algorithm(crc_width=0, polynomial=0x3, initial_crc=0x0,
                      reflect_input=False, reflect_output=False, xor_output=0x7)
        with self.assertRaises(ValueError):
            Algorithm(crc_width=3, polynomial=0x8, initial_crc=0x0,
                      reflect_input=False, reflect_output=False, xor_output=0x7)
        with self.assertRaises(ValueError):
            Algorithm(crc_width=3, polynomial=0x3, initial_crc=0x8,
                      reflect_input=False, reflect_output=False, xor_output=0x7)
        with self.assertRaises(ValueError):
            Algorithm(crc_width=3, polynomial=0x3, initial_crc=0x0,
                      reflect_input=False, reflect_output=False, xor_output=0x8)

    def test_parameter_range_checks(self):
        with self.assertRaises(ValueError):
            catalog.CRC8_AUTOSAR(data_width=0)
        with self.assertRaises(ValueError):
            crc = catalog.CRC8_AUTOSAR()
            crc.compute([3, 4, 256])

    def test_crc_bytes(self):
        """
        Verify CRC generation by computing the check value for each CRC
        in the catalogue with byte-sized inputs.
        """
        for name in CRCS:
            crc = getattr(catalog, name)(data_width=8).create()
            check = CRC_CHECKS[name][0]

            def process():
                for word in b"123456789":
                    yield crc.start.eq(word == b"1")
                    yield crc.data.eq(word)
                    yield crc.valid.eq(1)
                    yield
                yield crc.valid.eq(0)
                yield
                self.assertEqual((yield crc.crc), check)

            sim = Simulator(crc)
            sim.add_sync_process(process)
            sim.add_clock(1e-6)
            sim.run()

    def test_crc_words(self):
        """
        Verify CRC generation for non-byte-sized data by computing a check
        value for 1, 2, 4, 16, 32, and 64-bit inputs.
        """
        # We can't use the catalogue check value since it requires 8-bit
        # inputs, so we'll instead use an input of b"12345678".
        data = b"12345678"
        # Split data into individual bits. When input is reflected, we have
        # to reflect each byte first, then form the input words, then let
        # the CRC module reflect those words, to get the same effective input.
        bits = "".join(f"{x:08b}" for x in data)
        bits_r = "".join(f"{x:08b}"[::-1] for x in data)

        for name in CRCS:
            for m in (1, 2, 4, 16, 32, 64):
                algo = getattr(catalog, name)
                crc = algo(data_width=m).create()
                # Use a SoftwareCRC with byte inputs to compute new checks.
                swcrc = algo(data_width=8)
                check = swcrc.compute(data)
                # Chunk input bits into m-bit words, reflecting if needed.
                if algo.reflect_input:
                    d = [bits_r[i : i+m][::-1] for i in range(0, len(bits), m)]
                else:
                    d = [bits[i : i+m] for i in range(0, len(bits), m)]
                words = [int(x, 2) for x in d]

                def process():
                    yield crc.start.eq(1)
                    yield
                    yield crc.start.eq(0)
                    for word in words:
                        yield crc.data.eq(word)
                        yield crc.valid.eq(1)
                        yield
                    yield crc.valid.eq(0)
                    yield
                    self.assertEqual((yield crc.crc), check)

                sim = Simulator(crc)
                sim.add_sync_process(process)
                sim.add_clock(1e-6)
                sim.run()

    def test_crc_match(self):
        """Verify match_detected output detects valid codewords."""
        for name in CRCS:
            algo = getattr(catalog, name)
            n = algo.crc_width
            m = 8 if n % 8 == 0 else 1
            crc = algo(data_width=m).create()
            check = CRC_CHECKS[name][0]

            if m == 8:
                # For CRCs which are multiples of one byte wide, we can easily
                # append the correct checksum in bytes.
                check_b = check.to_bytes(n // 8, "little" if algo.reflect_output else "big")
                words = b"123456789" + check_b
            else:
                # For other CRC sizes, use single-bit input data.
                if algo.reflect_output:
                    check_b = check.to_bytes((n + 7)//8, "little")
                    if not algo.reflect_input:
                        # For cross-endian CRCs, flip the CRC bits separately.
                        check_b = bytearray(int(f"{x:08b}"[::-1], 2) for x in check_b)
                else:
                    shift = 8 - (n % 8)
                    check_b = (check << shift).to_bytes((n + 7)//8, "big")
                    # No catalogue CRCs have ref_in but not ref_out.
                codeword = b"123456789" + check_b
                words = []
                for byte in codeword:
                    if algo.reflect_input:
                        words += [int(x) for x in f"{byte:08b}"[::-1]]
                    else:
                        words += [int(x) for x in f"{byte:08b}"]
                words = words[:72 + n]

            def process():
                yield crc.start.eq(1)
                yield
                yield crc.start.eq(0)
                for word in words:
                    yield crc.data.eq(word)
                    yield crc.valid.eq(1)
                    yield
                yield crc.valid.eq(0)
                yield
                self.assertTrue((yield crc.match_detected))

            sim = Simulator(crc)
            sim.add_sync_process(process)
            sim.add_clock(1e-6)
            sim.run()


# tests/test_lib_data.py

from enum import Enum
from unittest import TestCase

from amaranth.hdl import *
from amaranth.hdl.ast import ShapeCastable
from amaranth.lib.data import *
from amaranth.sim import Simulator

from .utils import *


class MockShapeCastable(ShapeCastable):
    def __init__(self, shape):
        self.shape = shape

    def as_shape(self):
        return self.shape

    def __call__(self, value):
        return value

    def const(self, init):
        return Const(init, self.shape)


class FieldTestCase(TestCase):
    def test_construct(self):
        f = Field(unsigned(2), 1)
        self.assertEqual(f.shape, unsigned(2))
        self.assertEqual(f.offset, 1)
        self.assertEqual(f.width, 2)

    def test_repr(self):
        f = Field(unsigned(2), 1)
        self.assertEqual(repr(f), "Field(unsigned(2), 1)")

    def test_equal(self):
        f1 = Field(unsigned(2), 1)
        f2 = Field(unsigned(2), 0)
        self.assertNotEqual(f1, f2)
        f3 = Field(unsigned(2), 1)
        self.assertEqual(f1, f3)
        f4 = Field(2, 1)
        self.assertEqual(f1, f4)
        f5 = Field(MockShapeCastable(unsigned(2)), 1)
        self.assertEqual(f1, f5)
        self.assertNotEqual(f1, object())

    def test_preserve_shape(self):
        sc = MockShapeCastable(unsigned(2))
        f = Field(sc, 0)
        self.assertEqual(f.shape, sc)
        self.assertEqual(f.width, 2)

    def test_shape_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Field shape must be a shape-castable object, not <.+>$"):
            Field(object(), 0)

    def test_offset_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Field offset must be a non-negative integer, not <.+>$"):
            Field(unsigned(2), object())
        with self.assertRaisesRegex(TypeError,
                r"^Field offset must be a non-negative integer, not -1$"):
            Field(unsigned(2), -1)

    def test_immutable(self):
        with self.assertRaises(AttributeError):
            Field(1, 0).shape = unsigned(2)
        with self.assertRaises(AttributeError):
            Field(1, 0).offset = 1


class StructLayoutTestCase(TestCase):
    def test_construct(self):
        sl = StructLayout({
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(sl.members, {
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(sl.size, 3)
        self.assertEqual(list(iter(sl)), [
            ("a", Field(unsigned(1), 0)),
            ("b", Field(2, 1))
        ])
        self.assertEqual(sl["a"], Field(unsigned(1), 0))
        self.assertEqual(sl["b"], Field(2, 1))

    def test_size_empty(self):
        self.assertEqual(StructLayout({}).size, 0)

    def test_eq(self):
        self.assertEqual(StructLayout({"a": unsigned(1), "b": 2}),
                         StructLayout({"a": unsigned(1), "b": unsigned(2)}))
        self.assertNotEqual(StructLayout({"a": unsigned(1), "b": 2}),
                            StructLayout({"b": unsigned(2), "a": unsigned(1)}))
        self.assertNotEqual(StructLayout({"a": unsigned(1), "b": 2}),
                            StructLayout({"a": unsigned(1)}))

    def test_repr(self):
        sl = StructLayout({
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(repr(sl), "StructLayout({'a': unsigned(1), 'b': 2})")

    def test_members_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Struct layout members must be provided as a mapping, not <.+>$"):
            StructLayout(object())

    def test_member_key_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Struct layout member name must be a string, not 1\.0$"):
            StructLayout({1.0: unsigned(1)})

    def test_member_value_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Struct layout member shape must be a shape-castable object, not 1\.0$"):
            StructLayout({"a": 1.0})


class UnionLayoutTestCase(TestCase):
    def test_construct(self):
        ul = UnionLayout({
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(ul.members, {
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(ul.size, 2)
        self.assertEqual(list(iter(ul)), [
            ("a", Field(unsigned(1), 0)),
            ("b", Field(2, 0))
        ])
        self.assertEqual(ul["a"], Field(unsigned(1), 0))
        self.assertEqual(ul["b"], Field(2, 0))

    def test_size_empty(self):
        self.assertEqual(UnionLayout({}).size, 0)

    def test_eq(self):
        self.assertEqual(UnionLayout({"a": unsigned(1), "b": 2}),
                         UnionLayout({"a": unsigned(1), "b": unsigned(2)}))
        self.assertEqual(UnionLayout({"a": unsigned(1), "b": 2}),
                         UnionLayout({"b": unsigned(2), "a": unsigned(1)}))
        self.assertNotEqual(UnionLayout({"a": unsigned(1), "b": 2}),
                            UnionLayout({"a": unsigned(1)}))

    def test_repr(self):
        ul = UnionLayout({
            "a": unsigned(1),
            "b": 2
        })
        self.assertEqual(repr(ul), "UnionLayout({'a': unsigned(1), 'b': 2})")

    def test_members_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Union layout members must be provided as a mapping, not <.+>$"):
            UnionLayout(object())

    def test_member_key_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Union layout member name must be a string, not 1\.0$"):
            UnionLayout({1.0: unsigned(1)})

    def test_member_value_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Union layout member shape must be a shape-castable object, not 1\.0$"):
            UnionLayout({"a": 1.0})

    def test_const_two_members_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^Initializer for at most one field can be provided for a union layout "
                r"\(specified: a, b\)$"):
            UnionLayout({"a": 1, "b": 2}).const(dict(a=1, b=2))


class ArrayLayoutTestCase(TestCase):
    def test_construct(self):
        al = ArrayLayout(unsigned(2), 3)
        self.assertEqual(al.elem_shape, unsigned(2))
        self.assertEqual(al.length, 3)
        self.assertEqual(list(iter(al)), [
            (0, Field(unsigned(2), 0)),
            (1, Field(unsigned(2), 2)),
            (2, Field(unsigned(2), 4)),
        ])
        self.assertEqual(al[0], Field(unsigned(2), 0))
        self.assertEqual(al[1], Field(unsigned(2), 2))
        self.assertEqual(al[2], Field(unsigned(2), 4))
        self.assertEqual(al[-1], Field(unsigned(2), 4))
        self.assertEqual(al[-2], Field(unsigned(2), 2))
        self.assertEqual(al[-3], Field(unsigned(2), 0))
        self.assertEqual(al.size, 6)

    def test_shape_castable(self):
        al = ArrayLayout(2, 3)
        self.assertEqual(al.size, 6)

    def test_eq(self):
        self.assertEqual(ArrayLayout(unsigned(2), 3),
                         ArrayLayout(unsigned(2), 3))
        self.assertNotEqual(ArrayLayout(unsigned(2), 3),
                            ArrayLayout(unsigned(2), 4))

    def test_repr(self):
        al = ArrayLayout(unsigned(2), 3)
        self.assertEqual(repr(al), "ArrayLayout(unsigned(2), 3)")

    def test_elem_shape_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Array layout element shape must be a shape-castable object, not <.+>$"):
            ArrayLayout(object(), 1)

    def test_length_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Array layout length must be a non-negative integer, not <.+>$"):
            ArrayLayout(unsigned(1), object())
        with self.assertRaisesRegex(TypeError,
                r"^Array layout length must be a non-negative integer, not -1$"):
            ArrayLayout(unsigned(1), -1)

    def test_key_wrong_bounds(self):
        al = ArrayLayout(unsigned(2), 3)
        with self.assertRaisesRegex(KeyError, r"^4$"):
            al[4]
        with self.assertRaisesRegex(KeyError, r"^-4$"):
            al[-4]

    def test_key_wrong_type(self):
        al = ArrayLayout(unsigned(2), 3)
        with self.assertRaisesRegex(TypeError,
                r"^Cannot index array layout with 'a'$"):
            al["a"]


class FlexibleLayoutTestCase(TestCase):
    def test_construct(self):
        il = FlexibleLayout(8, {
            "a": Field(unsigned(1), 1),
            "b": Field(unsigned(3), 0),
            0: Field(unsigned(2), 5)
        })
        self.assertEqual(il.size, 8)
        self.assertEqual(il.fields, {
            "a": Field(unsigned(1), 1),
            "b": Field(unsigned(3), 0),
            0: Field(unsigned(2), 5)
        })
        self.assertEqual(list(iter(il)), [
            ("a", Field(unsigned(1), 1)),
            ("b", Field(unsigned(3), 0)),
            (0, Field(unsigned(2), 5))
        ])
        self.assertEqual(il["a"], Field(unsigned(1), 1))
        self.assertEqual(il["b"], Field(unsigned(3), 0))
        self.assertEqual(il[0], Field(unsigned(2), 5))

    def test_is_not_mutated(self):
        il = FlexibleLayout(8, {"a": Field(unsigned(1), 0)})
        del il.fields["a"]
        self.assertIn("a", il.fields)

    def test_eq(self):
        self.assertEqual(FlexibleLayout(3, {"a": Field(unsigned(1), 0)}),
                         FlexibleLayout(3, {"a": Field(unsigned(1), 0)}))
        self.assertNotEqual(FlexibleLayout(3, {"a": Field(unsigned(1), 0)}),
                            FlexibleLayout(4, {"a": Field(unsigned(1), 0)}))
        self.assertNotEqual(FlexibleLayout(3, {"a": Field(unsigned(1), 0)}),
                            FlexibleLayout(3, {"a": Field(unsigned(1), 1)}))

    def test_eq_duck(self):
        self.assertEqual(FlexibleLayout(3, {"a": Field(unsigned(1), 0),
                                             "b": Field(unsigned(2), 1)}),
                         StructLayout({"a": unsigned(1),
                                       "b": unsigned(2)}))
        self.assertEqual(FlexibleLayout(2, {"a": Field(unsigned(1), 0),
                                             "b": Field(unsigned(2), 0)}),
                         UnionLayout({"a": unsigned(1),
                                      "b": unsigned(2)}))

    def test_repr(self):
        il = FlexibleLayout(8, {
            "a": Field(unsigned(1), 1),
            "b": Field(unsigned(3), 0),
            0: Field(unsigned(2), 5)
        })
        self.assertEqual(repr(il), "FlexibleLayout(8, {"
            "'a': Field(unsigned(1), 1), "
            "'b': Field(unsigned(3), 0), "
            "0: Field(unsigned(2), 5)})")

    def test_fields_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Flexible layout fields must be provided as a mapping, not <.+>$"):
            FlexibleLayout(8, object())

    def test_field_key_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Flexible layout field name must be a non-negative integer or a string, "
                r"not 1\.0$"):
            FlexibleLayout(8, {1.0: unsigned(1)})
        with self.assertRaisesRegex(TypeError,
                r"^Flexible layout field name must be a non-negative integer or a string, "
                r"not -1$"):
            FlexibleLayout(8, {-1: unsigned(1)})

    def test_field_value_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Flexible layout field value must be a Field instance, not 1\.0$"):
            FlexibleLayout(8, {"a": 1.0})

    def test_size_wrong_negative(self):
        with self.assertRaisesRegex(TypeError,
                r"^Flexible layout size must be a non-negative integer, not -1$"):
            FlexibleLayout(-1, {})

    def test_size_wrong_small(self):
        with self.assertRaisesRegex(ValueError,
                r"^Flexible layout field 'a' ends at bit 8, exceeding the size of 4 bit\(s\)$"):
            FlexibleLayout(4, {"a": Field(unsigned(8), 0)})
        with self.assertRaisesRegex(ValueError,
                r"^Flexible layout field 'a' ends at bit 5, exceeding the size of 4 bit\(s\)$"):
            FlexibleLayout(4, {"a": Field(unsigned(2), 3)})

    def test_key_wrong_missing(self):
        il = FlexibleLayout(8, {"a": Field(unsigned(2), 3)})
        with self.assertRaisesRegex(KeyError,
                r"^0$"):
            il[0]

    def test_key_wrong_type(self):
        il = FlexibleLayout(8, {"a": Field(unsigned(2), 3)})
        with self.assertRaisesRegex(TypeError,
                r"^Cannot index flexible layout with <.+>$"):
            il[object()]


class LayoutTestCase(FHDLTestCase):
    def test_cast(self):
        sl = StructLayout({})
        self.assertIs(Layout.cast(sl), sl)

    def test_cast_wrong_not_layout(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object unsigned\(1\) cannot be converted to a data layout$"):
            Layout.cast(unsigned(1))

    def test_cast_wrong_type(self):
        with self.assertRaisesRegex(TypeError,
                r"^Object <.+> cannot be converted to an Amaranth shape$"):
            Layout.cast(object())

    def test_cast_wrong_recur(self):
        sc = MockShapeCastable(None)
        sc.shape = sc
        with self.assertRaisesRegex(RecursionError,
                r"^Shape-castable object <.+> casts to itself$"):
            Layout.cast(sc)

    def test_eq_wrong_recur(self):
        sc = MockShapeCastable(None)
        sc.shape = sc
        self.assertNotEqual(StructLayout({}), sc)

    def test_call(self):
        sl = StructLayout({"f": unsigned(1)})
        s = Signal(1)
        v = sl(s)
        self.assertIs(v.shape(), sl)
        self.assertIs(v.as_value(), s)

    def test_const(self):
        sl = StructLayout({
            "a": unsigned(1),
            "b": unsigned(2)
        })
        self.assertRepr(sl.const(None), "(const 3'd0)")
        self.assertRepr(sl.const({"a": 0b1, "b": 0b10}), "(const 3'd5)")

        fl = FlexibleLayout(2, {
            "a": Field(unsigned(1), 0),
            "b": Field(unsigned(2), 0)
        })
        self.assertRepr(fl.const({"a": 0b11}), "(const 2'd1)")
        self.assertRepr(fl.const({"b": 0b10}), "(const 2'd2)")
        self.assertRepr(fl.const({"a": 0b1, "b": 0b10}), "(const 2'd2)")

        sls = StructLayout({
            "a": signed(4),
            "b": signed(4)
        })
        self.assertRepr(sls.const({"b": 0, "a": -1}), "(const 8'd15)")

    def test_const_wrong(self):
        sl = StructLayout({"f": unsigned(1)})
        with self.assertRaisesRegex(TypeError,
                r"^Layout constant initializer must be a mapping or a sequence, not "
                r"<.+?object.+?>$"):
            sl.const(object())

    def test_const_field_shape_castable(self):
        class CastableFromHex(ShapeCastable):
            def as_shape(self):
                return unsigned(8)

            def __call__(self, value):
                return value

            def const(self, init):
                return int(init, 16)

        sl = StructLayout({"f": CastableFromHex()})
        self.assertRepr(sl.const({"f": "aa"}), "(const 8'd170)")

        with self.assertRaisesRegex(ValueError,
                r"^Constant returned by <.+?CastableFromHex.+?>\.const\(\) must have the shape "
                r"that it casts to, unsigned\(8\), and not unsigned\(1\)$"):
            sl.const({"f": "01"})

    def test_const_field_const(self):
        sl = StructLayout({"f": unsigned(1)})
        self.assertRepr(sl.const({"f": Const(1)}), "(const 1'd1)")

    def test_signal_reset(self):
        sl = StructLayout({
            "a": unsigned(1),
            "b": unsigned(2)
        })
        self.assertEqual(Signal(sl).as_value().reset, 0)
        self.assertEqual(Signal(sl, reset={"a": 0b1, "b": 0b10}).as_value().reset, 5)


class ViewTestCase(FHDLTestCase):
    def test_construct(self):
        s = Signal(3)
        v = View(StructLayout({"a": unsigned(1), "b": unsigned(2)}), s)
        self.assertIs(Value.cast(v), s)
        self.assertRepr(v["a"], "(slice (sig s) 0:1)")
        self.assertRepr(v["b"], "(slice (sig s) 1:3)")

    def test_construct_signal(self):
        v = Signal(StructLayout({"a": unsigned(1), "b": unsigned(2)}))
        cv = Value.cast(v)
        self.assertIsInstance(cv, Signal)
        self.assertEqual(cv.shape(), unsigned(3))
        self.assertEqual(cv.name, "v")

    def test_construct_signal_reset(self):
        v1 = Signal(StructLayout({"a": unsigned(1), "b": unsigned(2)}),
                   reset={"a": 0b1, "b": 0b10})
        self.assertEqual(Value.cast(v1).reset, 0b101)
        v2 = Signal(StructLayout({"a": unsigned(1),
                                "b": StructLayout({"x": unsigned(1), "y": unsigned(1)})}),
                   reset={"a": 0b1, "b": {"x": 0b0, "y": 0b1}})
        self.assertEqual(Value.cast(v2).reset, 0b101)
        v3 = Signal(ArrayLayout(unsigned(2), 2),
                   reset=[0b01, 0b10])
        self.assertEqual(Value.cast(v3).reset, 0b1001)

    def test_layout_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^View layout must be a layout, not <.+?>$"):
            View(object(), Signal(1))

    def test_layout_conflict_with_attr(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^View layout includes a field 'as_value' that will be shadowed by the view "
                r"attribute 'amaranth\.lib\.data\.View\.as_value'$"):
            View(StructLayout({"as_value": unsigned(1)}), Signal(1))

    def test_layout_conflict_with_attr_derived(self):
        class DerivedView(View):
            def foo(self):
                pass
        with self.assertWarnsRegex(SyntaxWarning,
                r"^View layout includes a field 'foo' that will be shadowed by the view "
                r"attribute 'tests\.test_lib_data\.ViewTestCase\."
                r"test_layout_conflict_with_attr_derived\.<locals>.DerivedView\.foo'$"):
            DerivedView(StructLayout({"foo": unsigned(1)}), Signal(1))

    def test_target_wrong_type(self):
        with self.assertRaisesRegex(TypeError,
                r"^View target must be a value-castable object, not <.+?>$"):
            View(StructLayout({}), object())

    def test_target_wrong_size(self):
        with self.assertRaisesRegex(ValueError,
                r"^View target is 2 bit\(s\) wide, which is not compatible with the 1 bit\(s\) "
                r"wide view layout$"):
            View(StructLayout({"a": unsigned(1)}), Signal(2))

    def test_getitem(self):
        v = Signal(UnionLayout({
            "a": unsigned(2),
            "s": StructLayout({
                "b": unsigned(1),
                "c": unsigned(3)
            }),
            "p": 1,
            "q": signed(1),
            "r": ArrayLayout(unsigned(2), 2),
            "t": ArrayLayout(StructLayout({
                "u": unsigned(1),
                "v": unsigned(1)
            }), 2),
        }))
        cv = Value.cast(v)
        i = Signal(1)
        self.assertEqual(cv.shape(), unsigned(4))
        self.assertRepr(v["a"], "(slice (sig v) 0:2)")
        self.assertEqual(v["a"].shape(), unsigned(2))
        self.assertRepr(v["s"]["b"], "(slice (slice (sig v) 0:4) 0:1)")
        self.assertRepr(v["s"]["c"], "(slice (slice (sig v) 0:4) 1:4)")
        self.assertRepr(v["p"], "(slice (sig v) 0:1)")
        self.assertEqual(v["p"].shape(), unsigned(1))
        self.assertRepr(v["q"], "(s (slice (sig v) 0:1))")
        self.assertEqual(v["q"].shape(), signed(1))
        self.assertRepr(v["r"][0], "(slice (slice (sig v) 0:4) 0:2)")
        self.assertRepr(v["r"][1], "(slice (slice (sig v) 0:4) 2:4)")
        self.assertRepr(v["r"][i], "(part (slice (sig v) 0:4) (sig i) 2 2)")
        self.assertRepr(v["t"][0]["u"], "(slice (slice (slice (sig v) 0:4) 0:2) 0:1)")
        self.assertRepr(v["t"][1]["v"], "(slice (slice (slice (sig v) 0:4) 2:4) 1:2)")

    def test_getitem_custom_call(self):
        class Reverser(ShapeCastable):
            def as_shape(self):
                return unsigned(2)

            def __call__(self, value):
                return value[::-1]

            def const(self, init):
                return Const(init, 2)

        v = Signal(StructLayout({
            "f": Reverser()
        }))
        self.assertRepr(v.f, "(cat (slice (slice (sig v) 0:2) 1:2) "
                             "     (slice (slice (sig v) 0:2) 0:1))")

    def test_getitem_custom_call_wrong(self):
        class WrongCastable(ShapeCastable):
            def as_shape(self):
                return unsigned(2)

            def __call__(self, value):
                pass

            def const(self, init):
                return Const(init, 2)

        v = Signal(StructLayout({
            "f": WrongCastable()
        }))
        with self.assertRaisesRegex(TypeError,
                r"^<.+?\.WrongCastable.+?>\.__call__\(\) must return a value or a value-castable "
                r"object, not None$"):
            v.f

    def test_index_wrong_missing(self):
        with self.assertRaisesRegex(KeyError,
                r"^'a'$"):
            Signal(StructLayout({}))["a"]

    def test_index_wrong_struct_dynamic(self):
        with self.assertRaisesRegex(TypeError,
                r"^Only views with array layout, not StructLayout\(\{\}\), may be indexed "
                r"with a value$"):
            Signal(StructLayout({}))[Signal(1)]

    def test_getattr(self):
        v = Signal(UnionLayout({
            "a": unsigned(2),
            "s": StructLayout({
                "b": unsigned(1),
                "c": unsigned(3)
            }),
            "p": 1,
            "q": signed(1),
        }))
        cv = Value.cast(v)
        i = Signal(1)
        self.assertEqual(cv.shape(), unsigned(4))
        self.assertRepr(v.a, "(slice (sig v) 0:2)")
        self.assertEqual(v.a.shape(), unsigned(2))
        self.assertRepr(v.s.b, "(slice (slice (sig v) 0:4) 0:1)")
        self.assertRepr(v.s.c, "(slice (slice (sig v) 0:4) 1:4)")
        self.assertRepr(v.p, "(slice (sig v) 0:1)")
        self.assertEqual(v.p.shape(), unsigned(1))
        self.assertRepr(v.q, "(s (slice (sig v) 0:1))")
        self.assertEqual(v.q.shape(), signed(1))

    def test_getattr_reserved(self):
        v = Signal(UnionLayout({
            "_a": unsigned(2)
        }))
        self.assertRepr(v["_a"], "(slice (sig v) 0:2)")

    def test_attr_wrong_missing(self):
        with self.assertRaisesRegex(AttributeError,
                r"^View of \(sig \$signal\) does not have a field 'a'; "
                r"did you mean one of: 'b', 'c'\?$"):
            Signal(StructLayout({"b": unsigned(1), "c": signed(1)})).a

    def test_attr_wrong_reserved(self):
        with self.assertRaisesRegex(AttributeError,
                r"^View of \(sig \$signal\) field '_c' has a reserved name "
                r"and may only be accessed by indexing$"):
            Signal(StructLayout({"_c": signed(1)}))._c

    def test_signal_like(self):
        s1 = Signal(StructLayout({"a": unsigned(1)}))
        s2 = Signal.like(s1)
        self.assertEqual(s2.shape(), StructLayout({"a": unsigned(1)}))

    def test_bug_837_array_layout_getitem_str(self):
        with self.assertRaisesRegex(TypeError,
                r"^Views with array layout may only be indexed with an integer or a value, "
                r"not 'reset'$"):
            Signal(ArrayLayout(unsigned(1), 1), reset=[0])["reset"]

    def test_bug_837_array_layout_getattr(self):
        with self.assertRaisesRegex(AttributeError,
                r"^View of \(sig \$signal\) with an array layout does not have fields$"):
            Signal(ArrayLayout(unsigned(1), 1), reset=[0]).reset


class StructTestCase(FHDLTestCase):
    def test_construct(self):
        class S(Struct):
            a: unsigned(1)
            b: signed(3)

        self.assertEqual(Shape.cast(S), unsigned(4))
        self.assertEqual(Layout.cast(S), StructLayout({
            "a": unsigned(1),
            "b": signed(3)
        }))

        v = Signal(S)
        self.assertEqual(v.shape(), S)
        self.assertEqual(Value.cast(v).shape(), Shape.cast(S))
        self.assertEqual(Value.cast(v).name, "v")
        self.assertRepr(v.a, "(slice (sig v) 0:1)")
        self.assertRepr(v.b, "(s (slice (sig v) 1:4))")

    def test_construct_nested(self):
        Q = StructLayout({"r": signed(2), "s": signed(2)})

        class R(Struct):
            p: 4
            q: Q

        class S(Struct):
            a: unsigned(1)
            b: R

        self.assertEqual(Shape.cast(S), unsigned(9))

        v = Signal(S)
        self.assertIs(v.shape(), S)
        self.assertIsInstance(v, S)
        self.assertIs(v.b.shape(), R)
        self.assertIsInstance(v.b, R)
        self.assertIs(v.b.q.shape(), Q)
        self.assertIsInstance(v.b.q, View)
        self.assertRepr(v.b.p, "(slice (slice (sig v) 1:9) 0:4)")
        self.assertRepr(v.b.q.as_value(), "(slice (slice (sig v) 1:9) 4:8)")
        self.assertRepr(v.b.q.r, "(s (slice (slice (slice (sig v) 1:9) 4:8) 0:2))")
        self.assertRepr(v.b.q.s, "(s (slice (slice (slice (sig v) 1:9) 4:8) 2:4))")

    def test_construct_reset(self):
        class S(Struct):
            p: 4
            q: 2 = 1

        with self.assertRaises(AttributeError):
            S.q

        v1 = Signal(S)
        self.assertEqual(v1.as_value().reset, 0b010000)
        v2 = Signal(S, reset=dict(p=0b0011))
        self.assertEqual(v2.as_value().reset, 0b010011)
        v3 = Signal(S, reset=dict(p=0b0011, q=0b00))
        self.assertEqual(v3.as_value().reset, 0b000011)

    def test_shape_undefined_wrong(self):
        class S(Struct):
            pass

        with self.assertRaisesRegex(TypeError,
                r"^Aggregate class '.+?\.S' does not have a defined shape$"):
            Shape.cast(S)

    def test_base_class_1(self):
        class Sb(Struct):
            def add(self):
                return self.a + self.b

        class Sb1(Sb):
            a: 1
            b: 1

        class Sb2(Sb):
            a: 2
            b: 2

        self.assertEqual(Signal(Sb1).add().shape(), unsigned(2))
        self.assertEqual(Signal(Sb2).add().shape(), unsigned(3))

    def test_base_class_2(self):
        class Sb(Struct):
            a: 2
            b: 2

        class Sb1(Sb):
            def do(self):
                return Cat(self.a, self.b)

        class Sb2(Sb):
            def do(self):
                return self.a + self.b

        self.assertEqual(Signal(Sb1).do().shape(), unsigned(4))
        self.assertEqual(Signal(Sb2).do().shape(), unsigned(3))

    def test_layout_redefined_wrong(self):
        class Sb(Struct):
            a: 1

        with self.assertRaisesRegex(TypeError,
                r"^Aggregate class 'Sd' must either inherit or specify a layout, not both$"):
            class Sd(Sb):
                b: 1

    def test_typing_annotation_coexistence(self):
        class S(Struct):
            a: unsigned(1)
            b: int
            c: str = "x"

        self.assertEqual(Layout.cast(S), StructLayout({"a": unsigned(1)}))
        self.assertEqual(S.__annotations__, {"b": int, "c": str})
        self.assertEqual(S.c, "x")

    def test_signal_like(self):
        class S(Struct):
            a: 1
        s1 = Signal(S)
        s2 = Signal.like(s1)
        self.assertEqual(s2.shape(), S)


class UnionTestCase(FHDLTestCase):
    def test_construct(self):
        class U(Union):
            a: unsigned(1)
            b: signed(3)

        self.assertEqual(Shape.cast(U), unsigned(3))
        self.assertEqual(Layout.cast(U), UnionLayout({
            "a": unsigned(1),
            "b": signed(3)
        }))

        v = Signal(U)
        self.assertEqual(v.shape(), U)
        self.assertEqual(Value.cast(v).shape(), Shape.cast(U))
        self.assertRepr(v.a, "(slice (sig v) 0:1)")
        self.assertRepr(v.b, "(s (slice (sig v) 0:3))")

    def test_define_reset_two_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^Reset value for at most one field can be provided for a union class "
                r"\(specified: a, b\)$"):
            class U(Union):
                a: unsigned(1) = 1
                b: unsigned(2) = 1

    def test_construct_reset_two_wrong(self):
        class U(Union):
            a: unsigned(1)
            b: unsigned(2)

        with self.assertRaisesRegex(TypeError,
                r"^Reset value must be a constant initializer of <class '.+?\.U'>$") as cm:
            Signal(U, reset=dict(a=1, b=2))
            self.assertRegex(cm.exception.__cause__.message,
                             r"^Initializer for at most one field can be provided for a union "
                             r"class \(specified: a, b\)$")

    def test_construct_reset_override(self):
        class U(Union):
            a: unsigned(1) = 1
            b: unsigned(2)

        self.assertEqual(Signal(U).as_value().reset, 0b01)
        self.assertEqual(Signal(U, reset=dict(b=0b10)).as_value().reset, 0b10)


# Examples from https://github.com/amaranth-lang/amaranth/issues/693
class RFCExamplesTestCase(TestCase):
    @staticmethod
    def simulate(m):
        def wrapper(fn):
            sim = Simulator(m)
            sim.add_process(fn)
            sim.run()
        return wrapper

    def test_rfc_example_1(self):
        class Float32(Struct):
            fraction: unsigned(23)
            exponent: unsigned(8)
            sign: unsigned(1)

        self.assertEqual(Float32.as_shape().size, 32)

        flt_a = Float32(Signal(32))
        flt_b = Float32(Const(0b00111110001000000000000000000000, 32))

        m1 = Module()
        with m1.If(flt_b.fraction > 0):
            m1.d.comb += [
                flt_a.sign.eq(1),
                flt_a.exponent.eq(127)
            ]

        @self.simulate(m1)
        def check_m1():
            self.assertEqual((yield flt_a.as_value()), 0xbf800000)

        class FloatOrInt32(Union):
            float: Float32
            int: signed(32)

        f_or_i = Signal(FloatOrInt32)
        is_gt_1 = Signal()
        m2 = Module()
        m2.d.comb += [
            f_or_i.int.eq(0x41C80000),
            is_gt_1.eq(f_or_i.float.exponent >= 127) # => 1
        ]

        @self.simulate(m2)
        def check_m2():
            self.assertEqual((yield is_gt_1), 1)

        class Op(Enum):
          ADD = 0
          SUB = 1

        adder_op_layout = StructLayout({
            "op": Op,
            "a": Float32,
            "b": Float32
        })

        adder_op = Signal(adder_op_layout)
        self.assertEqual(len(adder_op.as_value()), 65)

        m3 = Module()
        m3.d.comb += [
            adder_op.eq(Op.SUB),
            adder_op.a.eq(flt_a),
            adder_op.b.eq(flt_b)
        ]

        @self.simulate(m3)
        def check_m3():
            self.assertEqual((yield adder_op.as_value()), 0x7c40000000000001)

    def test_rfc_example_2(self):
        class Kind(Enum):
            ONE_SIGNED = 0
            TWO_UNSIGNED = 1

        layout1 = StructLayout({
            "kind": Kind,
            "value": UnionLayout({
                "one_signed": signed(2),
                "two_unsigned": ArrayLayout(unsigned(1), 2)
            })
        })
        self.assertEqual(layout1.size, 3)

        view1 = Signal(layout1)
        self.assertIsInstance(view1, View)
        self.assertEqual(view1.shape(), layout1)
        self.assertEqual(view1.as_value().shape(), unsigned(3))

        m1 = Module()
        m1.d.comb += [
            view1.kind.eq(Kind.TWO_UNSIGNED),
            view1.value.two_unsigned[0].eq(1),
        ]

        @self.simulate(m1)
        def check_m1():
            self.assertEqual((yield view1.as_value()), 0b011)

        class SomeVariant(Struct):
            class Value(Union):
                one_signed: signed(2)
                two_unsigned: ArrayLayout(unsigned(1), 2)

            kind: Kind
            value: Value

        self.assertEqual(Shape.cast(SomeVariant), unsigned(3))

        view2 = Signal(SomeVariant)
        self.assertIsInstance(Value.cast(view2), Signal)
        self.assertEqual(Value.cast(view2).shape(), unsigned(3))

        m2 = Module()
        m2.submodules += m1
        m2.d.comb += [
            view2.kind.eq(Kind.ONE_SIGNED),
            view2.value.eq(view1.value)
        ]

        @self.simulate(m2)
        def check_m2():
            self.assertEqual((yield view2.as_value()), 0b010)

        layout2 = StructLayout({
            "ready": unsigned(1),
            "payload": SomeVariant
        })
        self.assertEqual(layout2.size, 4)

        self.assertEqual(layout1, Layout.cast(SomeVariant))

        self.assertIs(SomeVariant, view2.shape())


# tests/test_lib_enum.py

import enum as py_enum

from amaranth import *
from amaranth.lib.enum import Enum

from .utils import *


class EnumTestCase(FHDLTestCase):
    def test_members_non_int(self):
        # Mustn't raise to be a drop-in replacement for Enum.
        class EnumA(Enum):
            A = "str"

    def test_members_const_non_int(self):
        class EnumA(Enum):
            A = C(0)
            B = C(1)
        self.assertIs(EnumA.A.value, 0)
        self.assertIs(EnumA.B.value, 1)
        self.assertEqual(Shape.cast(EnumA), unsigned(1))

    def test_shape_no_members(self):
        class EnumA(Enum):
            pass
        class PyEnumA(py_enum.Enum):
            pass
        self.assertEqual(Shape.cast(EnumA), unsigned(0))
        self.assertEqual(Shape.cast(PyEnumA), unsigned(0))

    def test_shape_explicit(self):
        class EnumA(Enum, shape=signed(2)):
            pass
        self.assertEqual(Shape.cast(EnumA), signed(2))

    def test_shape_explicit_cast(self):
        class EnumA(Enum, shape=range(10)):
            pass
        self.assertEqual(Shape.cast(EnumA), unsigned(4))

    def test_shape_implicit(self):
        class EnumA(Enum):
            A = 0
            B = 1
        self.assertEqual(Shape.cast(EnumA), unsigned(1))
        class EnumB(Enum):
            A = 0
            B = 5
        self.assertEqual(Shape.cast(EnumB), unsigned(3))
        class EnumC(Enum):
            A = 0
            B = -1
        self.assertEqual(Shape.cast(EnumC), signed(2))
        class EnumD(Enum):
            A = 3
            B = -5
        self.assertEqual(Shape.cast(EnumD), signed(4))

    def test_shape_members_non_const_non_int_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Value 'str' of enumeration member 'A' must be a constant-castable expression$"):
            class EnumA(Enum, shape=unsigned(1)):
                A = "str"

    def test_shape_explicit_wrong_signed_mismatch(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value -1 of enumeration member 'A' is signed, but the enumeration "
                r"shape is unsigned\(1\)$"):
            class EnumA(Enum, shape=unsigned(1)):
                A = -1

    def test_shape_explicit_wrong_too_wide(self):
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value 2 of enumeration member 'A' will be truncated to the enumeration "
                r"shape unsigned\(1\)$"):
            class EnumA(Enum, shape=unsigned(1)):
                A = 2
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value 1 of enumeration member 'A' will be truncated to the enumeration "
                r"shape signed\(1\)$"):
            class EnumB(Enum, shape=signed(1)):
                A = 1
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Value -2 of enumeration member 'A' will be truncated to the "
                r"enumeration shape signed\(1\)$"):
            class EnumC(Enum, shape=signed(1)):
                A = -2

    def test_value_shape_from_enum_member(self):
        class EnumA(Enum, shape=unsigned(10)):
            A = 1
        self.assertRepr(Value.cast(EnumA.A), "(const 10'd1)")

    def test_const_no_shape(self):
        class EnumA(Enum):
            Z = 0
            A = 10
            B = 20
        self.assertRepr(EnumA.const(None), "(const 5'd0)")
        self.assertRepr(EnumA.const(10), "(const 5'd10)")
        self.assertRepr(EnumA.const(EnumA.A), "(const 5'd10)")

    def test_const_shape(self):
        class EnumA(Enum, shape=8):
            Z = 0
            A = 10
        self.assertRepr(EnumA.const(None), "(const 8'd0)")
        self.assertRepr(EnumA.const(10), "(const 8'd10)")
        self.assertRepr(EnumA.const(EnumA.A), "(const 8'd10)")

    def test_shape_implicit_wrong_in_concat(self):
        class EnumA(Enum):
            A = 0
        with self.assertWarnsRegex(SyntaxWarning,
                r"^Argument #1 of Cat\(\) is an enumerated value <EnumA\.A: 0> without a defined "
                r"shape used in bit vector context; define the enumeration by inheriting from "
                r"the class in amaranth\.lib\.enum and specifying the 'shape=' keyword argument$"):
            Cat(EnumA.A)


# tests/test_lib_fifo.py

# amaranth: UnusedElaboratable=no

from amaranth.hdl import *
from amaranth.asserts import *
from amaranth.sim import *
from amaranth.lib.fifo import *

from .utils import *
from amaranth._utils import _ignore_deprecated


class FIFOTestCase(FHDLTestCase):
    def test_depth_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^FIFO width must be a non-negative integer, not -1$"):
            FIFOInterface(width=-1, depth=8, fwft=True)
        with self.assertRaisesRegex(TypeError,
                r"^FIFO depth must be a non-negative integer, not -1$"):
            FIFOInterface(width=8, depth=-1, fwft=True)

    def test_sync_depth(self):
        self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0)
        self.assertEqual(SyncFIFO(width=8, depth=1).depth, 1)
        self.assertEqual(SyncFIFO(width=8, depth=2).depth, 2)

    def test_sync_buffered_depth(self):
        self.assertEqual(SyncFIFOBuffered(width=8, depth=0).depth, 0)
        self.assertEqual(SyncFIFOBuffered(width=8, depth=1).depth, 1)
        self.assertEqual(SyncFIFOBuffered(width=8, depth=2).depth, 2)

    def test_async_depth(self):
        self.assertEqual(AsyncFIFO(width=8, depth=0 ).depth, 0)
        self.assertEqual(AsyncFIFO(width=8, depth=1 ).depth, 1)
        self.assertEqual(AsyncFIFO(width=8, depth=2 ).depth, 2)
        self.assertEqual(AsyncFIFO(width=8, depth=3 ).depth, 4)
        self.assertEqual(AsyncFIFO(width=8, depth=4 ).depth, 4)
        self.assertEqual(AsyncFIFO(width=8, depth=15).depth, 16)
        self.assertEqual(AsyncFIFO(width=8, depth=16).depth, 16)
        self.assertEqual(AsyncFIFO(width=8, depth=17).depth, 32)

    def test_async_depth_wrong(self):
        with self.assertRaisesRegex(ValueError,
                (r"^AsyncFIFO only supports depths that are powers of 2; "
                    r"requested exact depth 15 is not$")):
            AsyncFIFO(width=8, depth=15, exact_depth=True)

    def test_async_buffered_depth(self):
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=0 ).depth, 0)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=1 ).depth, 2)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=2 ).depth, 2)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=3 ).depth, 3)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=4 ).depth, 5)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=15).depth, 17)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=16).depth, 17)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=17).depth, 17)
        self.assertEqual(AsyncFIFOBuffered(width=8, depth=18).depth, 33)

    def test_async_buffered_depth_wrong(self):
        with self.assertRaisesRegex(ValueError,
                (r"^AsyncFIFOBuffered only supports depths that are one higher than powers of 2; "
                    r"requested exact depth 16 is not$")):
            AsyncFIFOBuffered(width=8, depth=16, exact_depth=True)


class FIFOModel(Elaboratable, FIFOInterface):
    """
    Non-synthesizable first-in first-out queue, implemented naively as a chain of registers.
    """
    def __init__(self, *, width, depth, fwft, r_domain, w_domain):
        super().__init__(width=width, depth=depth, fwft=fwft)

        self.r_domain = r_domain
        self.w_domain = w_domain

        self.level = Signal(range(self.depth + 1))
        self.r_level = Signal(range(self.depth + 1))
        self.w_level = Signal(range(self.depth + 1))

    def elaborate(self, platform):
        m = Module()

        storage = Memory(width=self.width, depth=self.depth)
        w_port  = m.submodules.w_port = storage.write_port(domain=self.w_domain)
        r_port  = m.submodules.r_port = storage.read_port (domain="comb")

        produce = Signal(range(self.depth))
        consume = Signal(range(self.depth))

        m.d.comb += self.r_rdy.eq(self.level > 0)
        m.d.comb += r_port.addr.eq((consume + 1) % self.depth)
        if self.fwft:
            m.d.comb += self.r_data.eq(r_port.data)
        with m.If(self.r_en & self.r_rdy):
            if not self.fwft:
                m.d[self.r_domain] += self.r_data.eq(r_port.data)
            m.d[self.r_domain] += consume.eq(r_port.addr)

        m.d.comb += self.w_rdy.eq(self.level < self.depth)
        m.d.comb += w_port.data.eq(self.w_data)
        with m.If(self.w_en & self.w_rdy):
            m.d.comb += w_port.addr.eq((produce + 1) % self.depth)
            m.d.comb += w_port.en.eq(1)
            m.d[self.w_domain] += produce.eq(w_port.addr)

        with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)):
            m.d.sync += self.level.eq(0)
        with m.Else():
            m.d.sync += self.level.eq(self.level
                + (self.w_rdy & self.w_en)
                - (self.r_rdy & self.r_en))

        m.d.comb += [
            self.r_level.eq(self.level),
            self.w_level.eq(self.level),
        ]
        m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain))

        return m


class FIFOModelEquivalenceSpec(Elaboratable):
    """
    The first-in first-out queue model equivalence specification: for any inputs and control
    signals, the behavior of the implementation under test exactly matches the ideal model,
    except for behavior not defined by the model.
    """
    def __init__(self, fifo, r_domain, w_domain):
        self.fifo = fifo

        self.r_domain = r_domain
        self.w_domain = w_domain

    @_ignore_deprecated
    def elaborate(self, platform):
        m = Module()
        m.submodules.dut  = dut  = self.fifo
        m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft,
                                             r_domain=self.r_domain, w_domain=self.w_domain)

        m.d.comb += [
            gold.r_en.eq(dut.r_rdy & dut.r_en),
            gold.w_en.eq(dut.w_en),
            gold.w_data.eq(dut.w_data),
        ]

        m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy))
        m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy))
        m.d.comb += Assert(dut.r_level == gold.r_level)
        m.d.comb += Assert(dut.w_level == gold.w_level)

        if dut.fwft:
            m.d.comb += Assert(dut.r_rdy
                               .implies(dut.r_data == gold.r_data))
        else:
            m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) &
                                Past(dut.r_en, domain=self.r_domain))
                               .implies(dut.r_data == gold.r_data))

        return m


class FIFOContractSpec(Elaboratable):
    """
    The first-in first-out queue contract specification: if two elements are written to the queue
    consecutively, they must be read out consecutively at some later point, no matter all other
    circumstances, with the exception of reset.
    """
    def __init__(self, fifo, *, r_domain, w_domain, bound):
        self.fifo     = fifo
        self.r_domain = r_domain
        self.w_domain = w_domain
        self.bound    = bound

    @_ignore_deprecated
    def elaborate(self, platform):
        m = Module()
        m.submodules.dut = fifo = self.fifo

        m.domains += ClockDomain("sync")
        m.d.comb += ResetSignal().eq(0)
        if self.w_domain != "sync":
            m.domains += ClockDomain(self.w_domain)
            m.d.comb += ResetSignal(self.w_domain).eq(0)
        if self.r_domain != "sync":
            m.domains += ClockDomain(self.r_domain)
            m.d.comb += ResetSignal(self.r_domain).eq(0)

        entry_1 = AnyConst(fifo.width)
        entry_2 = AnyConst(fifo.width)

        with m.FSM(domain=self.w_domain) as write_fsm:
            with m.State("WRITE-1"):
                with m.If(fifo.w_rdy):
                    m.d.comb += [
                        fifo.w_data.eq(entry_1),
                        fifo.w_en.eq(1)
                    ]
                    m.next = "WRITE-2"
            with m.State("WRITE-2"):
                with m.If(fifo.w_rdy):
                    m.d.comb += [
                        fifo.w_data.eq(entry_2),
                        fifo.w_en.eq(1)
                    ]
                    m.next = "DONE"
            with m.State("DONE"):
                pass

        with m.FSM(domain=self.r_domain) as read_fsm:
            read_1 = Signal(fifo.width)
            read_2 = Signal(fifo.width)
            with m.State("READ"):
                m.d.comb += fifo.r_en.eq(1)
                if fifo.fwft:
                    r_rdy = fifo.r_rdy
                else:
                    r_rdy = Past(fifo.r_rdy, domain=self.r_domain)
                with m.If(r_rdy):
                    m.d.sync += [
                        read_1.eq(read_2),
                        read_2.eq(fifo.r_data),
                    ]
                with m.If((read_1 == entry_1) & (read_2 == entry_2)):
                    m.next = "DONE"
            with m.State("DONE"):
                pass

        with m.If(Initial()):
            m.d.comb += Assume(write_fsm.ongoing("WRITE-1"))
            m.d.comb += Assume(read_fsm.ongoing("READ"))
        with m.If(Past(Initial(), self.bound - 1)):
            m.d.comb += Assert(read_fsm.ongoing("DONE"))

        with m.If(ResetSignal(domain=self.w_domain)):
            m.d.comb += Assert(~fifo.r_rdy)

        if self.w_domain != "sync" or self.r_domain != "sync":
            m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) |
                               Rose(ClockSignal(self.r_domain)))

        return m


class FIFOFormalCase(FHDLTestCase):
    def check_sync_fifo(self, fifo):
        self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"),
                          mode="bmc", depth=fifo.depth + 1)
        self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync",
                                           bound=fifo.depth * 2 + 1),
                          mode="hybrid", depth=fifo.depth * 2 + 1)

    def test_sync_fwft_pot(self):
        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True))

    def test_sync_fwft_npot(self):
        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True))

    def test_sync_not_fwft_pot(self):
        self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False))

    def test_sync_not_fwft_npot(self):
        self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False))

    def test_sync_buffered_pot(self):
        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4))

    def test_sync_buffered_potp1(self):
        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=5))

    def test_sync_buffered_potm1(self):
        self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=3))

    def check_async_fifo(self, fifo):
        # TODO: properly doing model equivalence checking on this likely requires multiclock,
        # which is not really documented nor is it clear how to use it.
        # self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"),
        #                   mode="bmc", depth=fifo.depth * 3 + 1)
        self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write",
                                           bound=fifo.depth * 4 + 1),
                          mode="hybrid", depth=fifo.depth * 4 + 1)

    def test_async(self):
        self.check_async_fifo(AsyncFIFO(width=8, depth=4))

    def test_async_buffered(self):
        self.check_async_fifo(AsyncFIFOBuffered(width=8, depth=4))


# we need this testcase because we cant do model equivalence checking on the async fifos (at the moment)
class AsyncFIFOSimCase(FHDLTestCase):
    def test_async_fifo_r_level_latency(self):
        fifo = AsyncFIFO(width=32, depth=10, r_domain="sync", w_domain="sync")

        ff_syncronizer_latency = 2

        def testbench():
            for i in range(10):
                yield fifo.w_data.eq(i)
                yield fifo.w_en.eq(1)
                yield

                if (i - ff_syncronizer_latency) > 0:
                    self.assertEqual((yield fifo.r_level), i - ff_syncronizer_latency)
                else:
                    self.assertEqual((yield fifo.r_level), 0)

        simulator = Simulator(fifo)
        simulator.add_clock(100e-6)
        simulator.add_sync_process(testbench)
        simulator.run()

    def check_async_fifo_level(self, fifo, fill_in, expected_level, read=False):
        write_done = Signal()

        def write_process():
            for i in range(fill_in):
                yield fifo.w_data.eq(i)
                yield fifo.w_en.eq(1)
                yield Tick("write")
            yield fifo.w_en.eq(0)
            yield Tick("write")
            yield Tick("write")
            self.assertEqual((yield fifo.w_level), expected_level)
            yield write_done.eq(1)

        def read_process():
            if read:
                yield fifo.r_en.eq(1)
            while not (yield write_done):
                yield Tick("read")
            self.assertEqual((yield fifo.r_level), expected_level)

        simulator = Simulator(fifo)
        simulator.add_clock(100e-6, domain="write")
        simulator.add_sync_process(write_process, domain="write")
        simulator.add_clock(50e-6, domain="read")
        simulator.add_sync_process(read_process, domain="read")
        with simulator.write_vcd("test.vcd"):
            simulator.run()

    def test_async_fifo_level(self):
        fifo = AsyncFIFO(width=32, depth=8, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=5, expected_level=5)

    def test_async_fifo_level_full(self):
        fifo = AsyncFIFO(width=32, depth=8, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=10, expected_level=8)

    def test_async_buffered_fifo_level(self):
        fifo = AsyncFIFOBuffered(width=32, depth=9, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=5, expected_level=5)

    def test_async_buffered_fifo_level_only_three(self):
        fifo = AsyncFIFOBuffered(width=32, depth=9, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=3, expected_level=3)

    def test_async_buffered_fifo_level_full(self):
        fifo = AsyncFIFOBuffered(width=32, depth=9, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=10, expected_level=9)

    def test_async_buffered_fifo_level_empty(self):
        fifo = AsyncFIFOBuffered(width=32, depth=9, r_domain="read", w_domain="write")
        self.check_async_fifo_level(fifo, fill_in=0, expected_level=0, read=True)


# tests/test_lib_io.py

from amaranth.hdl import *
from amaranth.hdl.rec import *
from amaranth.sim import *
from amaranth.lib.io import *

from .utils import *


class PinLayoutTestCase(FHDLTestCase):
    def assertLayoutEqual(self, layout, expected):
        casted_layout = {}
        for name, (shape, dir) in layout.items():
            casted_layout[name] = (Shape.cast(shape), dir)

        self.assertEqual(casted_layout, expected)


class PinLayoutCombTestCase(PinLayoutTestCase):
    def test_pin_layout_i(self):
        layout_1 = pin_layout(1, dir="i")
        self.assertLayoutEqual(layout_1.fields, {
            "i": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="i")
        self.assertLayoutEqual(layout_2.fields, {
            "i": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_o(self):
        layout_1 = pin_layout(1, dir="o")
        self.assertLayoutEqual(layout_1.fields, {
            "o": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="o")
        self.assertLayoutEqual(layout_2.fields, {
            "o": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_oe(self):
        layout_1 = pin_layout(1, dir="oe")
        self.assertLayoutEqual(layout_1.fields, {
            "o":  (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="oe")
        self.assertLayoutEqual(layout_2.fields, {
            "o":  (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

    def test_pin_layout_io(self):
        layout_1 = pin_layout(1, dir="io")
        self.assertLayoutEqual(layout_1.fields, {
            "i":  (unsigned(1), DIR_NONE),
            "o":  (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="io")
        self.assertLayoutEqual(layout_2.fields, {
            "i":  (unsigned(2), DIR_NONE),
            "o":  (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })


class PinLayoutSDRTestCase(PinLayoutTestCase):
    def test_pin_layout_i(self):
        layout_1 = pin_layout(1, dir="i", xdr=1)
        self.assertLayoutEqual(layout_1.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="i", xdr=1)
        self.assertLayoutEqual(layout_2.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_o(self):
        layout_1 = pin_layout(1, dir="o", xdr=1)
        self.assertLayoutEqual(layout_1.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="o", xdr=1)
        self.assertLayoutEqual(layout_2.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_oe(self):
        layout_1 = pin_layout(1, dir="oe", xdr=1)
        self.assertLayoutEqual(layout_1.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o":  (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="oe", xdr=1)
        self.assertLayoutEqual(layout_2.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o":  (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

    def test_pin_layout_io(self):
        layout_1 = pin_layout(1, dir="io", xdr=1)
        self.assertLayoutEqual(layout_1.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i":  (unsigned(1), DIR_NONE),
            "o_clk": (unsigned(1), DIR_NONE),
            "o":  (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="io", xdr=1)
        self.assertLayoutEqual(layout_2.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i":  (unsigned(2), DIR_NONE),
            "o_clk": (unsigned(1), DIR_NONE),
            "o":  (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })


class PinLayoutDDRTestCase(PinLayoutTestCase):
    def test_pin_layout_i(self):
        layout_1 = pin_layout(1, dir="i", xdr=2)
        self.assertLayoutEqual(layout_1.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i0": (unsigned(1), DIR_NONE),
            "i1": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="i", xdr=2)
        self.assertLayoutEqual(layout_2.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i0": (unsigned(2), DIR_NONE),
            "i1": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_o(self):
        layout_1 = pin_layout(1, dir="o", xdr=2)
        self.assertLayoutEqual(layout_1.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(1), DIR_NONE),
            "o1": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="o", xdr=2)
        self.assertLayoutEqual(layout_2.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(2), DIR_NONE),
            "o1": (unsigned(2), DIR_NONE),
        })

    def test_pin_layout_oe(self):
        layout_1 = pin_layout(1, dir="oe", xdr=2)
        self.assertLayoutEqual(layout_1.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(1), DIR_NONE),
            "o1": (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="oe", xdr=2)
        self.assertLayoutEqual(layout_2.fields, {
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(2), DIR_NONE),
            "o1": (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

    def test_pin_layout_io(self):
        layout_1 = pin_layout(1, dir="io", xdr=2)
        self.assertLayoutEqual(layout_1.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i0": (unsigned(1), DIR_NONE),
            "i1": (unsigned(1), DIR_NONE),
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(1), DIR_NONE),
            "o1": (unsigned(1), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })

        layout_2 = pin_layout(2, dir="io", xdr=2)
        self.assertLayoutEqual(layout_2.fields, {
            "i_clk": (unsigned(1), DIR_NONE),
            "i0": (unsigned(2), DIR_NONE),
            "i1": (unsigned(2), DIR_NONE),
            "o_clk": (unsigned(1), DIR_NONE),
            "o0": (unsigned(2), DIR_NONE),
            "o1": (unsigned(2), DIR_NONE),
            "oe": (unsigned(1), DIR_NONE),
        })


class PinTestCase(FHDLTestCase):
    def test_attributes(self):
        pin = Pin(2, dir="io", xdr=2)
        self.assertEqual(pin.width, 2)
        self.assertEqual(pin.dir,   "io")
        self.assertEqual(pin.xdr,   2)


# tests/test_lib_scheduler.py

# amaranth: UnusedElaboratable=no

import unittest

from amaranth.hdl import *
from amaranth.asserts import *
from amaranth.sim import *
from amaranth.lib.scheduler import *

from .utils import *


class RoundRobinTestCase(unittest.TestCase):
    def test_count(self):
        dut = RoundRobin(count=32)
        self.assertEqual(dut.count, 32)
        self.assertEqual(len(dut.requests), 32)
        self.assertEqual(len(dut.grant), 5)

    def test_wrong_count(self):
        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"):
            dut = RoundRobin(count="foo")
        with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"):
            dut = RoundRobin(count=-1)


class RoundRobinSimulationTestCase(unittest.TestCase):
    def test_count_one(self):
        dut = RoundRobin(count=1)
        sim = Simulator(dut)
        def process():
            yield dut.requests.eq(0)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 0)
            self.assertFalse((yield dut.valid))

            yield dut.requests.eq(1)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 0)
            self.assertTrue((yield dut.valid))
        sim.add_sync_process(process)
        sim.add_clock(1e-6)
        with sim.write_vcd("test.vcd"):
            sim.run()

    def test_transitions(self):
        dut = RoundRobin(count=3)
        sim = Simulator(dut)
        def process():
            yield dut.requests.eq(0b111)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 1)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b110)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 2)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b010)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 1)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b011)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 0)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b001)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 0)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b101)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 2)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b100)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 2)
            self.assertTrue((yield dut.valid))

            yield dut.requests.eq(0b000)
            yield; yield Delay(1e-8)
            self.assertFalse((yield dut.valid))

            yield dut.requests.eq(0b001)
            yield; yield Delay(1e-8)
            self.assertEqual((yield dut.grant), 0)
            self.assertTrue((yield dut.valid))
        sim.add_sync_process(process)
        sim.add_clock(1e-6)
        with sim.write_vcd("test.vcd"):
            sim.run()


# tests/test_lib_wiring.py

import unittest
from types import SimpleNamespace as NS

from amaranth import *
from amaranth.hdl.ast import ValueCastable
from amaranth.lib import data, enum
from amaranth.lib.wiring import Flow, In, Out, Member
from amaranth.lib.wiring import SignatureError, SignatureMembers, FlippedSignatureMembers
from amaranth.lib.wiring import Signature, FlippedSignature, Interface, FlippedInterface
from amaranth.lib.wiring import Component
from amaranth.lib.wiring import ConnectionError, connect, flipped


class FlowTestCase(unittest.TestCase):
    def test_flow_call(self):
        self.assertEqual(In(unsigned(1)), Member(Flow.In, unsigned(1)))
        self.assertEqual(Out(5), Member(Flow.Out, 5))

    def test_flow_repr(self):
        self.assertEqual(repr(Flow.In), "In")
        self.assertEqual(repr(Flow.Out), "Out")

    def test_flow_str(self):
        self.assertEqual(str(Flow.In), "In")
        self.assertEqual(str(Flow.Out), "Out")


class MemberTestCase(unittest.TestCase):
    def test_port_member(self):
        member = Member(In, unsigned(1))
        self.assertEqual(member.flow, In)
        self.assertEqual(member.is_port, True)
        self.assertEqual(member.shape, unsigned(1))
        self.assertEqual(member.reset, None)
        self.assertEqual(member.is_signature, False)
        with self.assertRaisesRegex(AttributeError,
                r"^A port member does not have a signature$"):
            member.signature
        self.assertEqual(member.dimensions, ())
        self.assertEqual(repr(member), "In(unsigned(1))")

    def test_port_member_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Port member description must be a shape-castable object or a signature, "
                r"not 'whatever'$"):
            Member(In, "whatever")

    def test_port_member_reset(self):
        member = Member(Out, unsigned(1), reset=1)
        self.assertEqual(member.flow, Out)
        self.assertEqual(member.shape, unsigned(1))
        self.assertEqual(member.reset, 1)
        self.assertEqual(repr(member._reset_as_const), repr(Const(1, 1)))
        self.assertEqual(repr(member), "Out(unsigned(1), reset=1)")

    def test_port_member_reset_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Port member reset value 'no' is not a valid constant initializer "
                r"for unsigned\(1\)$"):
            Member(In, 1, reset="no")

    def test_port_member_reset_shape_castable(self):
        layout = data.StructLayout({"a": 32})
        member = Member(In, layout, reset={"a": 1})
        self.assertEqual(member.flow, In)
        self.assertEqual(member.shape, layout)
        self.assertEqual(member.reset, {"a": 1})
        self.assertEqual(repr(member), "In(StructLayout({'a': 32}), reset={'a': 1})")

    def test_port_member_reset_shape_castable_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Port member reset value 'no' is not a valid constant initializer "
                r"for StructLayout\({'a': 32}\)$"):
            Member(In, data.StructLayout({"a": 32}), reset="no")

    def test_signature_member_out(self):
        sig = Signature({"data": Out(unsigned(32))})
        member = Member(Out, sig)
        self.assertEqual(member.flow, Out)
        self.assertEqual(member.is_port, False)
        with self.assertRaisesRegex(AttributeError,
                r"^A signature member does not have a shape$"):
            member.shape
        with self.assertRaisesRegex(AttributeError,
                r"^A signature member does not have a reset value$"):
            member.reset
        self.assertEqual(member.is_signature, True)
        self.assertEqual(member.signature, sig)
        self.assertEqual(member.dimensions, ())
        self.assertEqual(repr(member), "Out(Signature({'data': Out(unsigned(32))}))")

    def test_signature_member_in(self):
        sig = Signature({"data": In(unsigned(32))})
        member = Member(In, sig)
        self.assertEqual(member.flow, In)
        self.assertEqual(member.is_port, False)
        with self.assertRaisesRegex(AttributeError,
                r"^A signature member does not have a shape$"):
            member.shape
        with self.assertRaisesRegex(AttributeError,
                r"^A signature member does not have a reset value$"):
            member.reset
        self.assertEqual(member.is_signature, True)
        self.assertEqual(member.signature, sig.flip())
        self.assertEqual(member.dimensions, ())
        self.assertEqual(repr(member), "In(Signature({'data': In(unsigned(32))}))")

    def test_signature_member_wrong(self):
        with self.assertRaisesRegex(ValueError,
                r"^A signature member cannot have a reset value$"):
            Member(In, Signature({}), reset=1)

    def test_array(self):
        array_2 = Member(In, unsigned(1)).array(2)
        self.assertEqual(array_2.dimensions, (2,))
        self.assertEqual(repr(array_2), "In(unsigned(1)).array(2)")

        array_2_3 = Member(In, unsigned(1)).array(2, 3)
        self.assertEqual(array_2_3.dimensions, (2, 3))
        self.assertEqual(repr(array_2_3), "In(unsigned(1)).array(2, 3)")

        array_2_3_chained = Member(In, unsigned(1)).array(3).array(2)
        self.assertEqual(array_2_3_chained.dimensions, (2, 3))
        self.assertEqual(repr(array_2_3_chained), "In(unsigned(1)).array(2, 3)")

    def test_array_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^Member array dimensions must be non-negative integers, not -1$"):
            Member(In, unsigned(1)).array(-1)
        with self.assertRaisesRegex(TypeError,
                r"^Member array dimensions must be non-negative integers, not 'what'$"):
            Member(In, unsigned(1)).array("what")

    def test_flip(self):
        self.assertEqual(In(1).flip(), Out(1))
        self.assertEqual(Out(1).flip(), In(1))

    def test_equality(self):
        self.assertEqual(In(1), In(1))
        self.assertNotEqual(In(1), Out(1))
        self.assertNotEqual(In(1), In(1, reset=1))
        self.assertNotEqual(In(1), In(1, reset=0))
        self.assertEqual(In(1), In(1).array())
        self.assertNotEqual(In(1), In(1).array(1))
        sig = Signature({})
        self.assertEqual(In(sig), In(sig))
        self.assertNotEqual(In(1), In(Signature({})))


class SignatureMembersTestCase(unittest.TestCase):
    def test_contains(self):
        self.assertNotIn("a", SignatureMembers())
        self.assertIn("a", SignatureMembers({"a": In(1)}))

    def test_getitem(self):
        members = SignatureMembers({"a": In(1)})
        self.assertEqual(members["a"], In(1))

    def test_getitem_missing(self):
        members = SignatureMembers({"a": In(1)})
        with self.assertRaisesRegex(SignatureError,
                r"^Member 'b' is not a part of the signature$"):
            members["b"]

    def test_getitem_wrong(self):
        members = SignatureMembers({"a": In(1)})
        with self.assertRaisesRegex(TypeError,
                r"^Member name must be a string, not 1$"):
            members[1]
        with self.assertRaisesRegex(NameError,
                r"^Member name '_a' must be a valid, public Python attribute name$"):
            members["_a"]
        with self.assertRaisesRegex(NameError,
                r"^Member name cannot be 'signature'$"):
            members["signature"]

    def test_setitem(self):
        members = SignatureMembers()
        members["a"] = In(1)
        self.assertEqual(members["a"], In(1))

    def test_setitem_existing(self):
        members = SignatureMembers({"a": In(1)})
        with self.assertRaisesRegex(SignatureError,
                r"^Member 'a' already exists in the signature and cannot be replaced$"):
            members["a"] = Out(2)

    def test_setitem_wrong(self):
        members = SignatureMembers()
        with self.assertRaisesRegex(TypeError,
                r"^Member name must be a string, not 1$"):
            members[1] = Out(1)
        with self.assertRaisesRegex(TypeError,
                r"^Assigned value 1 must be a member; did you mean In\(1\) or Out\(1\)\?$"):
            members["a"] = 1
        with self.assertRaisesRegex(NameError,
                r"^Member name '_a' must be a valid, public Python attribute name$"):
            members["_a"] = Out(1)
        with self.assertRaisesRegex(NameError,
                r"^Member name cannot be 'signature'$"):
            members["signature"] = Out(1)

    def test_delitem(self):
        members = SignatureMembers()
        with self.assertRaisesRegex(SignatureError,
                r"^Members cannot be removed from a signature$"):
            del members["a"]

    def test_iter_len(self):
        members = SignatureMembers()
        self.assertEqual(list(iter(members)), [])
        self.assertEqual(len(members), 0)
        members["a"] = In(1)
        self.assertEqual(list(iter(members)), ["a"])
        self.assertEqual(len(members), 1)

    def test_iter_sorted(self):
        self.assertEqual(list(iter(SignatureMembers({"a": In(1), "b": Out(1)}))),
                         ["a", "b"])
        self.assertEqual(list(iter(SignatureMembers({"b": In(1), "a": Out(1)}))),
                         ["a", "b"])

    def test_iadd(self):
        members = SignatureMembers()
        members += {"a": In(1)}
        members += [("b", Out(1))]
        self.assertEqual(members, SignatureMembers({"a": In(1), "b": Out(1)}))

    def test_freeze(self):
        members = SignatureMembers({"a": In(1)})
        self.assertEqual(members.frozen, False)
        members.freeze()
        self.assertEqual(members.frozen, True)
        with self.assertRaisesRegex(SignatureError,
                r"^Cannot add members to a frozen signature$"):
            members += {"b": Out(1)}

    def test_freeze_rec(self):
        sig = Signature({})
        members = SignatureMembers({
            "a": In(1),
            "s": Out(sig)
        })
        self.assertEqual(members.frozen, False)
        self.assertEqual(sig.frozen, False)
        self.assertEqual(sig.members.frozen, False)
        members.freeze()
        self.assertEqual(members.frozen, True)
        self.assertEqual(sig.frozen, True)
        self.assertEqual(sig.members.frozen, True)

    def test_flatten(self):
        sig = Signature({
            "b": Out(1),
            "c": In(2)
        })
        members = SignatureMembers({
            "a": In(1),
            "s": Out(sig)
        })
        self.assertEqual(list(members.flatten()), [
            (("a",), In(1)),
            (("s",), Out(sig)),
            (("s", "b"), Out(1)),
            (("s", "c"), In(2)),
        ])

    def test_create(self):
        sig = Signature({
            "b": Out(2)
        })
        members = SignatureMembers({
            "a": In(1),
            "s": Out(sig)
        })
        attrs = members.create()
        self.assertEqual(list(attrs.keys()), ["a", "s"])
        self.assertIsInstance(attrs["a"], Signal)
        self.assertEqual(attrs["a"].shape(), unsigned(1))
        self.assertEqual(attrs["a"].name, "a")
        self.assertEqual(attrs["s"].b.shape(), unsigned(2))
        self.assertEqual(attrs["s"].b.name, "s__b")

    def test_create_reset(self):
        members = SignatureMembers({
            "a": In(1, reset=1),
        })
        attrs = members.create()
        self.assertEqual(attrs["a"].reset, 1)

    def test_create_tuple(self):
        sig = SignatureMembers({
            "a": Out(1).array(2, 3)
        })
        members = sig.create()
        self.assertEqual(len(members["a"]), 2)
        self.assertEqual(len(members["a"][0]), 3)
        self.assertEqual(len(members["a"][1]), 3)
        for x in members["a"]:
            for y in x:
                self.assertIsInstance(y, Signal)
        self.assertEqual(members["a"][1][2].name, "a__1__2")

    def test_repr(self):
        self.assertEqual(repr(SignatureMembers({})),
                         "SignatureMembers({})")
        self.assertEqual(repr(SignatureMembers({"a": In(1)})),
                         "SignatureMembers({'a': In(1)})")
        members = SignatureMembers({"b": Out(2)})
        members.freeze()
        self.assertEqual(repr(members),
                         "SignatureMembers({'b': Out(2)}).freeze()")


class FlippedSignatureMembersTestCase(unittest.TestCase):
    def test_basic(self):
        members = SignatureMembers({"a": In(1)})
        fmembers = members.flip()
        self.assertIsInstance(fmembers, FlippedSignatureMembers)
        self.assertIn("a", fmembers)
        self.assertEqual(fmembers["a"], Out(1))
        fmembers["b"] = Out(2)
        self.assertEqual(len(fmembers), 2)
        self.assertEqual(members["b"], In(2))
        self.assertEqual(list(fmembers), ["a", "b"])
        fmembers += {"c": In(2)}
        self.assertEqual(members["c"], Out(2))
        self.assertIs(fmembers.flip(), members)

    def test_eq(self):
        self.assertEqual(SignatureMembers({"a": In(1)}).flip(),
                         SignatureMembers({"a": In(1)}).flip())
        self.assertEqual(SignatureMembers({"a": In(1)}).flip(),
                         SignatureMembers({"a": Out(1)}))

    def test_delitem(self):
        fmembers = SignatureMembers().flip()
        with self.assertRaisesRegex(SignatureError,
                r"^Members cannot be removed from a signature$"):
            del fmembers["a"]

    def test_freeze(self):
        members = SignatureMembers({"a": In(1)})
        fmembers = members.flip()
        self.assertEqual(fmembers.frozen, False)
        fmembers.freeze()
        self.assertEqual(members.frozen, True)
        self.assertEqual(fmembers.frozen, True)

    def test_repr(self):
        fmembers = SignatureMembers({"a": In(1)}).flip()
        self.assertEqual(repr(fmembers), "SignatureMembers({'a': In(1)}).flip()")


class SignatureTestCase(unittest.TestCase):
    def test_create(self):
        sig = Signature({"a": In(1)})
        self.assertEqual(sig.members, SignatureMembers({"a": In(1)}))

    def test_eq(self):
        self.assertEqual(Signature({"a": In(1)}),
                         Signature({"a": In(1)}))
        self.assertNotEqual(Signature({"a": In(1)}),
                            Signature({"a": Out(1)}))

    def test_freeze(self):
        sig = Signature({"a": In(1)})
        self.assertEqual(sig.frozen, False)
        sig.freeze()
        self.assertEqual(sig.frozen, True)
        with self.assertRaisesRegex(SignatureError,
                r"^Cannot add members to a frozen signature$"):
            sig.members += {"b": Out(1)}

    def assertNotCompliant(self, reason_regex, sig, obj):
        self.assertFalse(sig.is_compliant(obj))
        reasons = []
        self.assertFalse(sig.is_compliant(obj, reasons=reasons))
        self.assertEqual(len(reasons), 1)
        self.assertRegex(reasons[0], reason_regex)

    def test_is_compliant(self):
        self.assertNotCompliant(
            r"^'obj' does not have an attribute 'a'$",
            sig=Signature({"a": In(1)}),
            obj=NS())
        self.assertNotCompliant(
            r"^'obj\.a' is expected to be a tuple or a list, but it is a \(sig \$signal\)$",
            sig=Signature({"a": In(1).array(2)}),
            obj=NS(a=Signal()))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to have dimension 2, but its length is 1$",
            sig=Signature({"a": In(1).array(2)}),
            obj=NS(a=[Signal()]))
        self.assertNotCompliant(
            r"^'obj\.a\[0\]' is expected to have dimension 2, but its length is 1$",
            sig=Signature({"a": In(1).array(1, 2)}),
            obj=NS(a=[[Signal()]]))
        self.assertNotCompliant(
            r"^'obj\.a' is not a value-castable object, but 'foo'$",
            sig=Signature({"a": In(1)}),
            obj=NS(a="foo"))
        self.assertNotCompliant(
            r"^'obj\.a' is neither a signal nor a constant, but "
            r"\(\+ \(const 1'd1\) \(const 1'd1\)\)$",
            sig=Signature({"a": In(1)}),
            obj=NS(a=Const(1)+1))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to have the shape unsigned\(1\), but "
            r"it has the shape unsigned\(2\)$",
            sig=Signature({"a": In(1)}),
            obj=NS(a=Signal(2)))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to have the shape unsigned\(1\), but "
            r"it has the shape signed\(1\)$",
            sig=Signature({"a": In(unsigned(1))}),
            obj=NS(a=Signal(signed(1))))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to have the reset value None, but it has the reset value 1$",
            sig=Signature({"a": In(1)}),
            obj=NS(a=Signal(reset=1)))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to have the reset value 1, but it has the reset value 0$",
            sig=Signature({"a": In(1, reset=1)}),
            obj=NS(a=Signal(1)))
        self.assertNotCompliant(
            r"^'obj\.a' is expected to not be reset-less$",
            sig=Signature({"a": In(1)}),
            obj=NS(a=Signal(1, reset_less=True)))
        self.assertNotCompliant(
            r"^'obj\.a' does not have an attribute 'b'$",
            sig=Signature({"a": Out(Signature({"b": In(1)}))}),
            obj=NS(a=Signal()))
        self.assertTrue(
            Signature({"a": In(1)}).is_compliant(
                NS(a=Signal())))
        self.assertTrue(
            Signature({"a": In(1)}).is_compliant(
                NS(a=Const(1))))
        self.assertTrue( # list
            Signature({"a": In(1).array(2, 2)}).is_compliant(
                NS(a=[[Const(1), Const(1)], [Signal(), Signal()]])))
        self.assertTrue( # tuple
            Signature({"a": In(1).array(2, 2)}).is_compliant(
                NS(a=((Const(1), Const(1)), (Signal(), Signal())))))
        self.assertTrue( # mixed list and tuple
            Signature({"a": In(1).array(2, 2)}).is_compliant(
                NS(a=[[Const(1), Const(1)], (Signal(), Signal())])))
        self.assertTrue(
            Signature({"a": Out(Signature({"b": In(1)}))}).is_compliant(
                NS(a=NS(b=Signal()))))

    def test_repr(self):
        sig = Signature({"a": In(1)})
        self.assertEqual(repr(sig), "Signature({'a': In(1)})")

    def test_repr_subclass(self):
        class S(Signature):
            def __init__(self):
                super().__init__({"a": In(1)})
        sig = S()
        self.assertRegex(repr(sig), r"^<.+\.S object at .+?>$")

    def test_subclasscheck(self):
        class S(Signature):
            pass
        self.assertTrue(issubclass(FlippedSignature, Signature))
        self.assertTrue(issubclass(Signature, Signature))
        self.assertTrue(issubclass(FlippedSignature, S))
        self.assertTrue(not issubclass(Signature, S))

    def test_instancecheck(self):
        class S(Signature):
            pass
        sig = Signature({})
        sig2 = S({})
        self.assertTrue(isinstance(sig.flip(), Signature))
        self.assertTrue(isinstance(sig2.flip(), Signature))
        self.assertTrue(not isinstance(sig.flip(), S))
        self.assertTrue(isinstance(sig2.flip(), S))


class FlippedSignatureTestCase(unittest.TestCase):
    def test_create(self):
        sig = Signature({"a": In(1)})
        fsig = sig.flip()
        self.assertIsInstance(fsig, FlippedSignature)
        self.assertIsInstance(fsig.members, FlippedSignatureMembers)
        self.assertIs(fsig.flip(), sig)

    def test_eq(self):
        self.assertEqual(Signature({"a": In(1)}).flip(),
                         Signature({"a": In(1)}).flip())
        self.assertEqual(Signature({"a": In(1)}).flip(),
                         Signature({"a": Out(1)}))

    def test_repr(self):
        sig = Signature({"a": In(1)}).flip()
        self.assertEqual(repr(sig), "Signature({'a': In(1)}).flip()")

    def test_getattr_setattr(self):
        class S(Signature):
            def __init__(self):
                super().__init__({})
                self.x = 1

            def f(self2):
                self.assertIsInstance(self2, FlippedSignature)
                return "f()"
        sig = S()
        fsig = sig.flip()
        self.assertEqual(fsig.x, 1)
        self.assertEqual(fsig.f(), "f()")
        fsig.y = 2
        self.assertEqual(sig.y, 2)


class InterfaceTestCase(unittest.TestCase):
    pass


class FlippedInterfaceTestCase(unittest.TestCase):
    def test_basic(self):
        sig = Signature({"a": In(1)})
        intf = sig.create()
        self.assertTrue(sig.is_compliant(intf))
        self.assertIs(intf.signature, sig)
        tintf = flipped(intf)
        self.assertEqual(tintf.signature, intf.signature.flip())
        self.assertEqual(tintf, flipped(intf))
        self.assertRegex(repr(tintf),
            r"^flipped\(<.+?\.Interface object at .+>\)$")

    def test_getattr_setattr(self):
        class I(Interface):
            signature = Signature({})

            def __init__(self):
                self.x = 1

            def f(self2):
                self.assertIsInstance(self2, FlippedInterface)
                return "f()"
        intf = I()
        tintf = flipped(intf)
        self.assertEqual(tintf.x, 1)
        self.assertEqual(tintf.f(), "f()")
        tintf.y = 2
        self.assertEqual(intf.y, 2)

    def test_flipped_wrong(self):
        with self.assertRaisesRegex(TypeError,
                r"^flipped\(\) can only flip an interface object, not Signature\({}\)$"):
            flipped(Signature({}))


class ConnectTestCase(unittest.TestCase):
    def test_arg_handles_and_signature_attr(self):
        m = Module()
        with self.assertRaisesRegex(AttributeError,
                r"^Argument 0 must have a 'signature' attribute$"):
            connect(m, object())
        with self.assertRaisesRegex(AttributeError,
                r"^Argument 'x' must have a 'signature' attribute$"):
            connect(m, x=object())

    def test_signature_type(self):
        m = Module()
        with self.assertRaisesRegex(TypeError,
                r"^Signature of argument 0 must be a signature, not 1$"):
            connect(m, NS(signature=1))

    def test_signature_compliant(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Argument 0 does not match its signature:\n"
                r"- 'arg0' does not have an attribute 'a'$"):
            connect(m, NS(signature=Signature({"a": In(1)})))

    def test_signature_freeze(self):
        m = Module()
        intf = NS(signature=Signature({}))
        connect(m, intf)
        self.assertTrue(intf.signature.frozen)

    def test_member_missing(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Member 'b' is present in 'q', but not in 'p'$"):
            connect(m,
                    p=NS(signature=Signature({"a": In(1)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": In(1), "b": Out(1)}),
                         a=Signal(), b=Signal()))
        with self.assertRaisesRegex(ConnectionError,
                r"^Member 'b' is present in 'p', but not in 'q'$"):
            connect(m,
                    p=NS(signature=Signature({"a": In(1), "b": Out(1)}),
                         a=Signal(), b=Signal()),
                    q=NS(signature=Signature({"a": In(1)}),
                         a=Signal()))

    def test_signature_to_port(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect signature member\(s\) 'p\.a' with port member\(s\) 'q\.a'$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(Signature({}))}),
                         a=NS(signature=Signature({}))),
                    q=NS(signature=Signature({"a": In(1)}),
                         a=Signal()))

    def test_shape_mismatch(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect the member 'q\.a' with shape unsigned\(2\) to the member 'p\.a' "
                r"with shape unsigned\(1\) because the shape widths \(2 and 1\) do not match$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": In(2)}),
                         a=Signal(2)))

    def test_shape_mismatch_enum(self):
        class Cycle(enum.Enum, shape=2):
            READ  = 0
            WRITE = 1

        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect the member 'q\.a' with shape unsigned\(2\) \(<enum 'Cycle'>\) "
                r"to the member 'p\.a' with shape unsigned\(1\) because the shape widths "
                r"\(2 and 1\) do not match$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": In(Cycle)}),
                         a=Signal(Cycle)))

    def test_reset_mismatch(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect together the member 'q\.a' with reset value 1 and the member "
                r"'p\.a' with reset value 0 because the reset values do not match$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1, reset=0)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": In(1, reset=1)}),
                         a=Signal(reset=1)))

    def test_reset_none_match(self):
        m = Module()
        connect(m,
                p=NS(signature=Signature({"a": Out(1, reset=0)}),
                     a=Signal()),
                q=NS(signature=Signature({"a": In(1)}),
                     a=Signal()))

    def test_out_to_out(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect several output members 'p\.a', 'q\.a' together$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": Out(1)}),
                         a=Signal()))

    def test_out_to_const_in(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect to the input member 'q\.a' that has a constant value 0$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1)}),
                         a=Signal()),
                    q=NS(signature=Signature({"a": In(1)}),
                         a=Const(0)))

    def test_const_out_to_const_in_value_mismatch(self):
        m = Module()
        with self.assertRaisesRegex(ConnectionError,
                r"^Cannot connect input member 'q\.a' that has a constant value 0 to an output "
                r"member 'p\.a' that has a differing constant value 1$"):
            connect(m,
                    p=NS(signature=Signature({"a": Out(1)}),
                         a=Const(1)),
                    q=NS(signature=Signature({"a": In(1)}),
                         a=Const(0)))

    def test_simple_bus(self):
        class Cycle(enum.Enum):
            IDLE  = 0
            READ  = 1
            WRITE = 2
        sig = Signature({
            "cycle":  Out(Cycle),
            "addr":   Out(16),
            "r_data": In(32),
            "w_data": Out(32),
        })

        src = sig.create(path=('src',))
        snk = sig.flip().create(path=('snk',))

        m = Module()
        connect(m, src=src, snk=snk)
        self.assertEqual([repr(stmt) for stmt in m._statements], [
            '(eq (sig snk__addr) (sig src__addr))',
            '(eq (sig snk__cycle) (sig src__cycle))',
            '(eq (sig src__r_data) (sig snk__r_data))',
            '(eq (sig snk__w_data) (sig src__w_data))'
        ])

    def test_const_in_out(self):
        m = Module()
        connect(m,
                p=NS(signature=Signature({"a": Out(1)}),
                     a=Const(1)),
                q=NS(signature=Signature({"a": In(1)}),
                     a=Const(1)))
        self.assertEqual(m._statements, [])

    def test_nested(self):
        m = Module()
        connect(m,
                p=NS(signature=Signature({"a": Out(Signature({"f": Out(1)}))}),
                     a=NS(f=Signal(name='p__a'))),
                q=NS(signature=Signature({"a": In(Signature({"f": Out(1)}))}),
                     a=NS(f=Signal(name='q__a'))))
        self.assertEqual([repr(stmt) for stmt in m._statements], [
            '(eq (sig q__a) (sig p__a))'
        ])

    def test_dimension(self):
        sig = Signature({"a": Out(1).array(2)})

        m = Module()
        connect(m, p=sig.create(path=('p',)), q=sig.flip().create(path=('q',)))
        self.assertEqual([repr(stmt) for stmt in m._statements], [
            '(eq (sig q__a__0) (sig p__a__0))',
            '(eq (sig q__a__1) (sig p__a__1))'
        ])

    def test_dimension_multi(self):
        sig = Signature({"a": Out(1).array(1).array(1)})

        m = Module()
        connect(m, p=sig.create(path=('p',)), q=sig.flip().create(path=('q',)))
        self.assertEqual([repr(stmt) for stmt in m._statements], [
            '(eq (sig q__a__0__0) (sig p__a__0__0))',
        ])


class ComponentTestCase(unittest.TestCase):
    def test_basic(self):
        class C(Component):
            sig : Out(2)

        c = C()
        self.assertEqual(c.signature, Signature({"sig": Out(2)}))
        self.assertIsInstance(c.sig, Signal)
        self.assertEqual(c.sig.shape(), unsigned(2))

    def test_non_member_annotations(self):
        class C(Component):
            sig : Out(2)
            foo : int

        c = C()
        self.assertEqual(c.signature, Signature({"sig": Out(2)}))

    def test_private_member_annotations(self):
        class C(Component):
            sig_pub : Out(2)
            _sig_priv : Out(2)

        c = C()
        self.assertEqual(c.signature, Signature({"sig_pub": Out(2)}))

    def test_no_annotations(self):
        class C(Component):
            pass

        with self.assertRaisesRegex(NotImplementedError,
                r"^Component '.+?\.C' does not have signature member annotations$"):
            C()

    def test_would_overwrite_field(self):
        class C(Component):
            sig : Out(2)

            def __init__(self):
                self.sig = 1
                super().__init__()

        with self.assertRaisesRegex(NameError,
                r"^Cannot initialize attribute for signature member 'sig' because an attribute "
                r"with the same name already exists$"):
            C()

    def test_missing_in_out_warning(self):
        class C1(Component):
            prt1 : In(1)
            sig2 : Signal

        with self.assertWarnsRegex(SyntaxWarning,
                r"^Component '.+\.C1' has an annotation 'sig2: Signal', which is not a signature "
                r"member; did you mean 'sig2: In\(Signal\)' or 'sig2: Out\(Signal\)'\?$"):
            C1().signature

        class C2(Component):
            prt1 : In(1)
            sig2 : Signature({})

        with self.assertWarnsRegex(SyntaxWarning,
                r"^Component '.+\.C2' has an annotation 'sig2: Signature\({}\)', which is not "
                r"a signature member; did you mean 'sig2: In\(Signature\({}\)\)' or "
                r"'sig2: Out\(Signature\({}\)\)'\?$"):
            C2().signature

        class MockValueCastable(ValueCastable):
            def shape(self): pass
            @ValueCastable.lowermethod
            def as_value(self): pass

        class C3(Component):
            prt1 : In(1)
            val2 : MockValueCastable

        with self.assertWarnsRegex(SyntaxWarning,
                r"^Component '.+\.C3' has an annotation 'val2: MockValueCastable', which is not "
                r"a signature member; did you mean 'val2: In\(MockValueCastable\)' or "
                r"'val2: Out\(MockValueCastable\)'\?$"):
            C3().signature

    def test_bug_882(self):
        class PageBuffer(Component):
            rand: Signature({}).flip()
            other: Out(1)

        with self.assertWarnsRegex(SyntaxWarning,
                r"^Component '.+\.PageBuffer' has an annotation 'rand: Signature\({}\)\.flip\(\)', "
                r"which is not a signature member; did you mean "
                r"'rand: In\(Signature\({}\)\.flip\(\)\)' or "
                r"'rand: Out\(Signature\({}\)\.flip\(\)\)'\?$"):
            PageBuffer()


# tests/test_sim.py

import os
import warnings
from contextlib import contextmanager

from amaranth._utils import flatten
from amaranth.hdl.ast import *
from amaranth.hdl.cd import  *
from amaranth.hdl.mem import *
from amaranth.hdl.rec import *
from amaranth.hdl.dsl import  *
from amaranth.hdl.ir import *
from amaranth.sim import *

from .utils import *
from amaranth._utils import _ignore_deprecated


class SimulatorUnitTestCase(FHDLTestCase):
    def assertStatement(self, stmt, inputs, output, reset=0):
        inputs = [Value.cast(i) for i in inputs]
        output = Value.cast(output)

        isigs = [Signal(i.shape(), name=n) for i, n in zip(inputs, "abcd")]
        osig  = Signal(output.shape(), name="y", reset=reset)

        stmt = stmt(osig, *isigs)
        frag = Fragment()
        frag.add_statements(stmt)
        for signal in flatten(s._lhs_signals() for s in Statement.cast(stmt)):
            frag.add_driver(signal)

        sim = Simulator(frag)
        def process():
            for isig, input in zip(isigs, inputs):
                yield isig.eq(input)
            yield Settle()
            self.assertEqual((yield osig), output.value)
        sim.add_process(process)
        with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]):
            sim.run()

    def test_invert(self):
        stmt = lambda y, a: y.eq(~a)
        self.assertStatement(stmt, [C(0b0000, 4)], C(0b1111, 4))
        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0101, 4))
        self.assertStatement(stmt, [C(0,      4)], C(-1,     4))

    def test_neg(self):
        stmt = lambda y, a: y.eq(-a)
        self.assertStatement(stmt, [C(0b0000, 4)], C(0b0000, 4))
        self.assertStatement(stmt, [C(0b0001, 4)], C(0b1111, 4))
        self.assertStatement(stmt, [C(0b1010, 4)], C(0b0110, 4))
        self.assertStatement(stmt, [C(1,      4)], C(-1,     4))
        self.assertStatement(stmt, [C(5,      4)], C(-5,     4))

    def test_bool(self):
        stmt = lambda y, a: y.eq(a.bool())
        self.assertStatement(stmt, [C(0, 4)], C(0))
        self.assertStatement(stmt, [C(1, 4)], C(1))
        self.assertStatement(stmt, [C(2, 4)], C(1))

    def test_as_unsigned(self):
        stmt = lambda y, a, b: y.eq(a.as_unsigned() == b)
        self.assertStatement(stmt, [C(0b01, signed(2)), C(0b0001, unsigned(4))], C(1))
        self.assertStatement(stmt, [C(0b11, signed(2)), C(0b0011, unsigned(4))], C(1))

    def test_as_unsigned_lhs(self):
        stmt = lambda y, a: y.as_unsigned().eq(a)
        self.assertStatement(stmt, [C(0b01, unsigned(2))], C(0b0001, signed(4)))

    def test_as_signed(self):
        stmt = lambda y, a, b: y.eq(a.as_signed() == b)
        self.assertStatement(stmt, [C(0b01, unsigned(2)), C(0b0001, signed(4))], C(1))
        self.assertStatement(stmt, [C(0b11, unsigned(2)), C(0b1111, signed(4))], C(1))

    def test_as_signed_issue_502(self):
        stmt = lambda y, a: y.eq(a.as_signed())
        self.assertStatement(stmt, [C(0b01, unsigned(2))], C(0b0001, signed(4)))
        self.assertStatement(stmt, [C(0b11, unsigned(2))], C(0b1111, signed(4)))

    def test_as_signed_lhs(self):
        stmt = lambda y, a: y.as_signed().eq(a)
        self.assertStatement(stmt, [C(0b01, unsigned(2))], C(0b0001, signed(4)))

    def test_any(self):
        stmt = lambda y, a: y.eq(a.any())
        self.assertStatement(stmt, [C(0b00, 2)], C(0))
        self.assertStatement(stmt, [C(0b01, 2)], C(1))
        self.assertStatement(stmt, [C(0b10, 2)], C(1))
        self.assertStatement(stmt, [C(0b11, 2)], C(1))

    def test_all(self):
        stmt = lambda y, a: y.eq(a.all())
        self.assertStatement(stmt, [C(0b00, 2)], C(0))
        self.assertStatement(stmt, [C(0b01, 2)], C(0))
        self.assertStatement(stmt, [C(0b10, 2)], C(0))
        self.assertStatement(stmt, [C(0b11, 2)], C(1))

    def test_xor_unary(self):
        stmt = lambda y, a: y.eq(a.xor())
        self.assertStatement(stmt, [C(0b00, 2)], C(0))
        self.assertStatement(stmt, [C(0b01, 2)], C(1))
        self.assertStatement(stmt, [C(0b10, 2)], C(1))
        self.assertStatement(stmt, [C(0b11, 2)], C(0))

    def test_add(self):
        stmt = lambda y, a, b: y.eq(a + b)
        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(1,   4))
        self.assertStatement(stmt, [C(-5, 4), C(-5, 4)], C(-10, 5))

    def test_sub(self):
        stmt = lambda y, a, b: y.eq(a - b)
        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(1,   4))
        self.assertStatement(stmt, [C(0,  4), C(1,  4)], C(-1,  4))
        self.assertStatement(stmt, [C(0,  4), C(10, 4)], C(-10, 5))

    def test_mul(self):
        stmt = lambda y, a, b: y.eq(a * b)
        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(4,   8))
        self.assertStatement(stmt, [C(7,  4), C(7,  4)], C(49,  8))

    def test_floordiv(self):
        stmt = lambda y, a, b: y.eq(a // b)
        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(2,   8))
        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(1,   8))
        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(3,   8))

    def test_floordiv_neg(self):
        stmt = lambda y, a, b: y.eq(a // b)
        self.assertStatement(stmt, [C(-5, 4), C( 2, 4)], C(-3, 8))
        self.assertStatement(stmt, [C(-5, 4), C(-2, 4)], C( 2, 8))
        self.assertStatement(stmt, [C( 5, 4), C( 2, 4)], C( 2, 8))
        self.assertStatement(stmt, [C( 5, 4), C(-2, 4)], C(-3, 8))

    def test_mod(self):
        stmt = lambda y, a, b: y.eq(a % b)
        self.assertStatement(stmt, [C(2,  4), C(0,  4)], C(0,   8))
        self.assertStatement(stmt, [C(2,  4), C(1,  4)], C(0,   8))
        self.assertStatement(stmt, [C(2,  4), C(2,  4)], C(0,   8))
        self.assertStatement(stmt, [C(7,  4), C(2,  4)], C(1,   8))

    def test_mod_neg(self):
        stmt = lambda y, a, b: y.eq(a % b)
        self.assertStatement(stmt, [C(-5, 4), C( 3, 4)], C( 1, 8))
        self.assertStatement(stmt, [C(-5, 4), C(-3, 4)], C(-2, 8))
        self.assertStatement(stmt, [C( 5, 4), C( 3, 4)], C( 2, 8))
        self.assertStatement(stmt, [C( 5, 4), C(-3, 4)], C(-1, 8))

    def test_and(self):
        stmt = lambda y, a, b: y.eq(a & b)
        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1000, 4))

    def test_or(self):
        stmt = lambda y, a, b: y.eq(a | b)
        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4))

    def test_xor_binary(self):
        stmt = lambda y, a, b: y.eq(a ^ b)
        self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4))

    def test_shl(self):
        stmt = lambda y, a, b: y.eq(a << b)
        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    5))
        self.assertStatement(stmt, [C(0b1001, 4), C(3)],  C(0b1001000, 7))

    def test_shr(self):
        stmt = lambda y, a, b: y.eq(a >> b)
        self.assertStatement(stmt, [C(0b1001, 4), C(0)],  C(0b1001,    4))
        self.assertStatement(stmt, [C(0b1001, 4), C(2)],  C(0b10,      4))

    def test_eq(self):
        stmt = lambda y, a, b: y.eq(a == b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))

    def test_ne(self):
        stmt = lambda y, a, b: y.eq(a != b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))

    def test_lt(self):
        stmt = lambda y, a, b: y.eq(a < b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))

    def test_ge(self):
        stmt = lambda y, a, b: y.eq(a >= b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))

    def test_gt(self):
        stmt = lambda y, a, b: y.eq(a > b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1))

    def test_le(self):
        stmt = lambda y, a, b: y.eq(a <= b)
        self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1))
        self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1))
        self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0))

    def test_mux(self):
        stmt = lambda y, a, b, c: y.eq(Mux(c, a, b))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(3, 4))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(2, 4))

    def test_mux_invert(self):
        stmt = lambda y, a, b, c: y.eq(Mux(~c, a, b))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(2, 4))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(3, 4))

    def test_mux_wide(self):
        stmt = lambda y, a, b, c: y.eq(Mux(c, a, b))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0, 2)], C(3, 4))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1, 2)], C(2, 4))
        self.assertStatement(stmt, [C(2, 4), C(3, 4), C(2, 2)], C(2, 4))

    def test_abs(self):
        stmt = lambda y, a: y.eq(abs(a))
        self.assertStatement(stmt, [C(3,  unsigned(8))], C(3,  unsigned(8)))
        self.assertStatement(stmt, [C(-3, unsigned(8))], C(-3, unsigned(8)))
        self.assertStatement(stmt, [C(3,  signed(8))],   C(3,  signed(8)))
        self.assertStatement(stmt, [C(-3, signed(8))],   C(3,  signed(8)))

    def test_slice(self):
        stmt1 = lambda y, a: y.eq(a[2])
        self.assertStatement(stmt1, [C(0b10110100, 8)], C(0b1,  1))
        stmt2 = lambda y, a: y.eq(a[2:4])
        self.assertStatement(stmt2, [C(0b10110100, 8)], C(0b01, 2))

    def test_slice_lhs(self):
        stmt1 = lambda y, a: y[2].eq(a)
        self.assertStatement(stmt1, [C(0b0,  1)], C(0b11111011, 8), reset=0b11111111)
        stmt2 = lambda y, a: y[2:4].eq(a)
        self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011)

    def test_bit_select(self):
        stmt = lambda y, a, b: y.eq(a.bit_select(b, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3))

    def test_bit_select_lhs(self):
        stmt = lambda y, a, b: y.bit_select(a, 3).eq(b)
        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
        self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111)
        self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111)

    def test_word_select(self):
        stmt = lambda y, a, b: y.eq(a.word_select(b, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3))
        self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3))

    def test_word_select_lhs(self):
        stmt = lambda y, a, b: y.word_select(a, 3).eq(b)
        self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111)
        self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111)
        self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111)

    def test_cat(self):
        stmt = lambda y, *xs: y.eq(Cat(*xs))
        self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4))

    def test_cat_lhs(self):
        l = Signal(3)
        m = Signal(3)
        n = Signal(3)
        stmt = lambda y, a: [Cat(l, m, n).eq(a), y.eq(Cat(n, m, l))]
        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))

    def test_nested_cat_lhs(self):
        l = Signal(3)
        m = Signal(3)
        n = Signal(3)
        stmt = lambda y, a: [Cat(Cat(l, Cat(m)), n).eq(a), y.eq(Cat(n, m, l))]
        self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9))

    def test_record(self):
        rec = Record([
            ("l", 1),
            ("m", 2),
        ])
        stmt = lambda y, a: [rec.eq(a), y.eq(rec)]
        self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3))

    def test_replicate(self):
        stmt = lambda y, a: y.eq(a.replicate(3))
        self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6))

    def test_array(self):
        array = Array([1, 4, 10])
        stmt = lambda y, a: y.eq(array[a])
        self.assertStatement(stmt, [C(0)], C(1))
        self.assertStatement(stmt, [C(1)], C(4))
        self.assertStatement(stmt, [C(2)], C(10))

    def test_array_oob(self):
        array = Array([1, 4, 10])
        stmt = lambda y, a: y.eq(array[a])
        self.assertStatement(stmt, [C(3)], C(10))
        self.assertStatement(stmt, [C(4)], C(10))

    def test_array_lhs(self):
        l = Signal(3, reset=1)
        m = Signal(3, reset=4)
        n = Signal(3, reset=7)
        array = Array([l, m, n])
        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
        self.assertStatement(stmt, [C(0), C(0b000)], C(0b111100000))
        self.assertStatement(stmt, [C(1), C(0b010)], C(0b111010001))
        self.assertStatement(stmt, [C(2), C(0b100)], C(0b100100001))

    def test_array_lhs_oob(self):
        l = Signal(3)
        m = Signal(3)
        n = Signal(3)
        array = Array([l, m, n])
        stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))]
        self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000))
        self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000))

    def test_array_index(self):
        array = Array(Array(x * y for y in range(10)) for x in range(10))
        stmt = lambda y, a, b: y.eq(array[a][b])
        for x in range(10):
            for y in range(10):
                self.assertStatement(stmt, [C(x), C(y)], C(x * y))

    def test_array_attr(self):
        from collections import namedtuple
        pair = namedtuple("pair", ("p", "n"))

        array = Array(pair(x, -x) for x in range(10))
        stmt = lambda y, a: y.eq(array[a].p + array[a].n)
        for i in range(10):
            self.assertStatement(stmt, [C(i)], C(0))

    def test_shift_left(self):
        stmt1 = lambda y, a: y.eq(a.shift_left(1))
        self.assertStatement(stmt1, [C(0b10100010, 8)], C(   0b101000100, 9))
        stmt2 = lambda y, a: y.eq(a.shift_left(4))
        self.assertStatement(stmt2, [C(0b10100010, 8)], C(0b101000100000, 12))

    def test_shift_right(self):
        stmt1 = lambda y, a: y.eq(a.shift_right(1))
        self.assertStatement(stmt1, [C(0b10100010, 8)], C(0b1010001, 7))
        stmt2 = lambda y, a: y.eq(a.shift_right(4))
        self.assertStatement(stmt2, [C(0b10100010, 8)], C(   0b1010, 4))

    def test_rotate_left(self):
        stmt = lambda y, a: y.eq(a.rotate_left(1))
        self.assertStatement(stmt, [C(0b1)], C(0b1))
        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
        stmt = lambda y, a: y.eq(a.rotate_left(5))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
        stmt = lambda y, a: y.eq(a.rotate_left(7))
        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
        stmt = lambda y, a: y.eq(a.rotate_left(9))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
        stmt = lambda y, a: y.eq(a.rotate_left(-1))
        self.assertStatement(stmt, [C(0b1)], C(0b1))
        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
        stmt = lambda y, a: y.eq(a.rotate_left(-5))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
        stmt = lambda y, a: y.eq(a.rotate_left(-7))
        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
        stmt = lambda y, a: y.eq(a.rotate_left(-9))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))

    def test_rotate_right(self):
        stmt = lambda y, a: y.eq(a.rotate_right(1))
        self.assertStatement(stmt, [C(0b1)], C(0b1))
        self.assertStatement(stmt, [C(0b1001000)], C(0b0100100))
        stmt = lambda y, a: y.eq(a.rotate_right(5))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))
        stmt = lambda y, a: y.eq(a.rotate_right(7))
        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
        stmt = lambda y, a: y.eq(a.rotate_right(9))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
        stmt = lambda y, a: y.eq(a.rotate_right(-1))
        self.assertStatement(stmt, [C(0b1)], C(0b1))
        self.assertStatement(stmt, [C(0b1001000)], C(0b0010001))
        stmt = lambda y, a: y.eq(a.rotate_right(-5))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0010000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0110000))
        stmt = lambda y, a: y.eq(a.rotate_right(-7))
        self.assertStatement(stmt, [C(0b1000000)], C(0b1000000))
        self.assertStatement(stmt, [C(0b1000001)], C(0b1000001))
        stmt = lambda y, a: y.eq(a.rotate_right(-9))
        self.assertStatement(stmt, [C(0b1000000)], C(0b0000010))
        self.assertStatement(stmt, [C(0b1000001)], C(0b0000110))


class SimulatorIntegrationTestCase(FHDLTestCase):
    @contextmanager
    def assertSimulation(self, module, deadline=None):
        sim = Simulator(module)
        yield sim
        with sim.write_vcd("test.vcd", "test.gtkw"):
            if deadline is None:
                sim.run()
            else:
                sim.run_until(deadline)

    def setUp_counter(self):
        self.count = Signal(3, reset=4)
        self.sync  = ClockDomain()

        self.m = Module()
        self.m.d.sync  += self.count.eq(self.count + 1)
        self.m.domains += self.sync

    def test_counter_process(self):
        self.setUp_counter()
        with self.assertSimulation(self.m) as sim:
            def process():
                self.assertEqual((yield self.count), 4)
                yield Delay(1e-6)
                self.assertEqual((yield self.count), 4)
                yield self.sync.clk.eq(1)
                self.assertEqual((yield self.count), 4)
                yield Settle()
                self.assertEqual((yield self.count), 5)
                yield Delay(1e-6)
                self.assertEqual((yield self.count), 5)
                yield self.sync.clk.eq(0)
                self.assertEqual((yield self.count), 5)
                yield Settle()
                self.assertEqual((yield self.count), 5)
                for _ in range(3):
                    yield Delay(1e-6)
                    yield self.sync.clk.eq(1)
                    yield Delay(1e-6)
                    yield self.sync.clk.eq(0)
                self.assertEqual((yield self.count), 0)
            sim.add_process(process)

    def test_counter_clock_and_sync_process(self):
        self.setUp_counter()
        with self.assertSimulation(self.m) as sim:
            sim.add_clock(1e-6, domain="sync")
            def process():
                self.assertEqual((yield self.count), 4)
                self.assertEqual((yield self.sync.clk), 1)
                yield
                self.assertEqual((yield self.count), 5)
                self.assertEqual((yield self.sync.clk), 1)
                for _ in range(3):
                    yield
                self.assertEqual((yield self.count), 0)
            sim.add_sync_process(process)

    def test_reset(self):
        self.setUp_counter()
        sim = Simulator(self.m)
        sim.add_clock(1e-6)
        times = 0
        def process():
            nonlocal times
            self.assertEqual((yield self.count), 4)
            yield
            self.assertEqual((yield self.count), 5)
            yield
            self.assertEqual((yield self.count), 6)
            yield
            times += 1
        sim.add_sync_process(process)
        sim.run()
        sim.reset()
        sim.run()
        self.assertEqual(times, 2)

    def setUp_alu(self):
        self.a = Signal(8)
        self.b = Signal(8)
        self.o = Signal(8)
        self.x = Signal(8)
        self.s = Signal(2)
        self.sync = ClockDomain(reset_less=True)

        self.m = Module()
        self.m.d.comb += self.x.eq(self.a ^ self.b)
        with self.m.Switch(self.s):
            with self.m.Case(0):
                self.m.d.sync += self.o.eq(self.a + self.b)
            with self.m.Case(1):
                self.m.d.sync += self.o.eq(self.a - self.b)
            with self.m.Case():
                self.m.d.sync += self.o.eq(0)
        self.m.domains += self.sync

    def test_alu(self):
        self.setUp_alu()
        with self.assertSimulation(self.m) as sim:
            sim.add_clock(1e-6)
            def process():
                yield self.a.eq(5)
                yield self.b.eq(1)
                yield
                self.assertEqual((yield self.x), 4)
                yield
                self.assertEqual((yield self.o), 6)
                yield self.s.eq(1)
                yield
                yield
                self.assertEqual((yield self.o), 4)
                yield self.s.eq(2)
                yield
                yield
                self.assertEqual((yield self.o), 0)
            sim.add_sync_process(process)

    def setUp_clock_phase(self):
        self.m = Module()
        self.phase0 = self.m.domains.phase0 = ClockDomain()
        self.phase90 = self.m.domains.phase90 = ClockDomain()
        self.phase180 = self.m.domains.phase180 = ClockDomain()
        self.phase270 = self.m.domains.phase270 = ClockDomain()
        self.check = self.m.domains.check = ClockDomain()

        self.expected = [
            [0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0],
            [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
            [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
        ]

    def test_clock_phase(self):
        self.setUp_clock_phase()
        with self.assertSimulation(self.m) as sim:
            period=1
            sim.add_clock(period/8, phase=0,          domain="check")
            sim.add_clock(period,   phase=0*period/4, domain="phase0")
            sim.add_clock(period,   phase=1*period/4, domain="phase90")
            sim.add_clock(period,   phase=2*period/4, domain="phase180")
            sim.add_clock(period,   phase=3*period/4, domain="phase270")

            def proc():
                clocks = [
                    self.phase0.clk,
                    self.phase90.clk,
                    self.phase180.clk,
                    self.phase270.clk
                ]
                for i in range(16):
                    yield
                    for j, c in enumerate(clocks):
                        self.assertEqual((yield c), self.expected[j][i])

            sim.add_sync_process(proc, domain="check")

    def setUp_multiclock(self):
        self.sys = ClockDomain()
        self.pix = ClockDomain()

        self.m = Module()
        self.m.domains += self.sys, self.pix

    def test_multiclock(self):
        self.setUp_multiclock()
        with self.assertSimulation(self.m) as sim:
            sim.add_clock(1e-6, domain="sys")
            sim.add_clock(0.3e-6, domain="pix")

            def sys_process():
                yield Passive()
                yield
                yield
                self.fail()
            def pix_process():
                yield
                yield
                yield
            sim.add_sync_process(sys_process, domain="sys")
            sim.add_sync_process(pix_process, domain="pix")

    def setUp_lhs_rhs(self):
        self.i = Signal(8)
        self.o = Signal(8)

        self.m = Module()
        self.m.d.comb += self.o.eq(self.i)

    def test_complex_lhs_rhs(self):
        self.setUp_lhs_rhs()
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.i.eq(0b10101010)
                yield self.i[:4].eq(-1)
                yield Settle()
                self.assertEqual((yield self.i[:4]), 0b1111)
                self.assertEqual((yield self.i), 0b10101111)
            sim.add_process(process)

    def test_run_until(self):
        m = Module()
        s = Signal()
        m.d.sync += s.eq(0)
        with self.assertSimulation(m, deadline=100e-6) as sim:
            sim.add_clock(1e-6)
            def process():
                for _ in range(101):
                    yield Delay(1e-6)
                self.fail()
            sim.add_process(process)

    def test_run_until_fail(self):
        m = Module()
        s = Signal()
        m.d.sync += s.eq(0)
        with self.assertRaises(AssertionError):
            with self.assertSimulation(m, deadline=100e-6) as sim:
                    sim.add_clock(1e-6)
                    def process():
                        for _ in range(99):
                            yield Delay(1e-6)
                        self.fail()
                    sim.add_process(process)

    def test_add_process_wrong(self):
        with self.assertSimulation(Module()) as sim:
            with self.assertRaisesRegex(TypeError,
                    r"^Cannot add a process 1 because it is not a generator function$"):
                sim.add_process(1)

    def test_add_process_wrong_generator(self):
        with self.assertSimulation(Module()) as sim:
            with self.assertRaisesRegex(TypeError,
                    r"^Cannot add a process <.+?> because it is not a generator function$"):
                def process():
                    yield Delay()
                sim.add_process(process())

    def test_add_clock_wrong_twice(self):
        m = Module()
        s = Signal()
        m.d.sync += s.eq(0)
        with self.assertSimulation(m) as sim:
            sim.add_clock(1)
            with self.assertRaisesRegex(ValueError,
                    r"^Domain 'sync' already has a clock driving it$"):
                sim.add_clock(1)

    def test_add_clock_wrong_missing(self):
        m = Module()
        with self.assertSimulation(m) as sim:
            with self.assertRaisesRegex(ValueError,
                    r"^Domain 'sync' is not present in simulation$"):
                sim.add_clock(1)

    def test_add_clock_if_exists(self):
        m = Module()
        with self.assertSimulation(m) as sim:
            sim.add_clock(1, if_exists=True)

    def test_command_wrong(self):
        survived = False
        with self.assertSimulation(Module()) as sim:
            def process():
                nonlocal survived
                with self.assertRaisesRegex(TypeError,
                        r"Received unsupported command 1 from process .+?"):
                    yield 1
                yield Settle()
                survived = True
            sim.add_process(process)
        self.assertTrue(survived)

    def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
        self.m = Module()
        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
        self.m.submodules.rdport = self.rdport = \
            self.memory.read_port(domain="sync" if rd_synchronous else "comb",
                                  transparent=rd_transparent)
        self.m.submodules.wrport = self.wrport = \
            self.memory.write_port(granularity=wr_granularity)

    def test_memory_init(self):
        self.setUp_memory()
        with self.assertSimulation(self.m) as sim:
            def process():
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield self.rdport.addr.eq(1)
                yield
                yield
                self.assertEqual((yield self.rdport.data), 0x55)
                yield self.rdport.addr.eq(2)
                yield
                yield
                self.assertEqual((yield self.rdport.data), 0x00)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    def test_memory_write(self):
        self.setUp_memory()
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.wrport.addr.eq(4)
                yield self.wrport.data.eq(0x33)
                yield self.wrport.en.eq(1)
                yield
                yield self.wrport.en.eq(0)
                yield self.rdport.addr.eq(4)
                yield
                self.assertEqual((yield self.rdport.data), 0x33)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    def test_memory_write_granularity(self):
        self.setUp_memory(wr_granularity=4)
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.wrport.data.eq(0x50)
                yield self.wrport.en.eq(0b00)
                yield
                yield self.wrport.en.eq(0)
                yield
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield self.wrport.en.eq(0b10)
                yield
                yield self.wrport.en.eq(0)
                yield
                self.assertEqual((yield self.rdport.data), 0x5a)
                yield self.wrport.data.eq(0x33)
                yield self.wrport.en.eq(0b01)
                yield
                yield self.wrport.en.eq(0)
                yield
                self.assertEqual((yield self.rdport.data), 0x53)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    def test_memory_read_before_write(self):
        self.setUp_memory(rd_transparent=False)
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.wrport.data.eq(0x33)
                yield self.wrport.en.eq(1)
                yield
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0x33)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    def test_memory_write_through(self):
        self.setUp_memory(rd_transparent=True)
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.wrport.data.eq(0x33)
                yield self.wrport.en.eq(1)
                yield
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0x33)
                yield
                yield self.rdport.addr.eq(1)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0x33)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    def test_memory_async_read_write(self):
        self.setUp_memory(rd_synchronous=False)
        with self.assertSimulation(self.m) as sim:
            def process():
                yield self.rdport.addr.eq(0)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield self.rdport.addr.eq(1)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0x55)
                yield self.rdport.addr.eq(0)
                yield self.wrport.addr.eq(0)
                yield self.wrport.data.eq(0x33)
                yield self.wrport.en.eq(1)
                yield Tick("sync")
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield Settle()
                self.assertEqual((yield self.rdport.data), 0x33)
            sim.add_clock(1e-6)
            sim.add_process(process)

    def test_memory_read_only(self):
        self.m = Module()
        self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])
        self.m.submodules.rdport = self.rdport = self.memory.read_port()
        with self.assertSimulation(self.m) as sim:
            def process():
                self.assertEqual((yield self.rdport.data), 0xaa)
                yield self.rdport.addr.eq(1)
                yield
                yield
                self.assertEqual((yield self.rdport.data), 0x55)
            sim.add_clock(1e-6)
            sim.add_sync_process(process)

    @_ignore_deprecated
    def test_sample_helpers(self):
        m = Module()
        s = Signal(2)
        def mk(x):
            y = Signal.like(x)
            m.d.comb += y.eq(x)
            return y
        p0, r0, f0, s0 = mk(Past(s, 0)), mk(Rose(s)),    mk(Fell(s)),    mk(Stable(s))
        p1, r1, f1, s1 = mk(Past(s)),    mk(Rose(s, 1)), mk(Fell(s, 1)), mk(Stable(s, 1))
        p2, r2, f2, s2 = mk(Past(s, 2)), mk(Rose(s, 2)), mk(Fell(s, 2)), mk(Stable(s, 2))
        p3, r3, f3, s3 = mk(Past(s, 3)), mk(Rose(s, 3)), mk(Fell(s, 3)), mk(Stable(s, 3))
        with self.assertSimulation(m) as sim:
            def process_gen():
                yield s.eq(0b10)
                yield
                yield
                yield s.eq(0b01)
                yield
            def process_check():
                yield
                yield
                yield

                self.assertEqual((yield p0), 0b01)
                self.assertEqual((yield p1), 0b10)
                self.assertEqual((yield p2), 0b10)
                self.assertEqual((yield p3), 0b00)

                self.assertEqual((yield s0), 0b0)
                self.assertEqual((yield s1), 0b1)
                self.assertEqual((yield s2), 0b0)
                self.assertEqual((yield s3), 0b1)

                self.assertEqual((yield r0), 0b01)
                self.assertEqual((yield r1), 0b00)
                self.assertEqual((yield r2), 0b10)
                self.assertEqual((yield r3), 0b00)

                self.assertEqual((yield f0), 0b10)
                self.assertEqual((yield f1), 0b00)
                self.assertEqual((yield f2), 0b00)
                self.assertEqual((yield f3), 0b00)
            sim.add_clock(1e-6)
            sim.add_sync_process(process_gen)
            sim.add_sync_process(process_check)

    def test_vcd_wrong_nonzero_time(self):
        s = Signal()
        m = Module()
        m.d.sync += s.eq(s)
        sim = Simulator(m)
        sim.add_clock(1e-6)
        sim.run_until(1e-5)
        with self.assertRaisesRegex(ValueError,
                r"^Cannot start writing waveforms after advancing simulation time$"):
            with open(os.path.devnull, "w") as f:
                with sim.write_vcd(f):
                    pass

    def test_no_negated_boolean_warning(self):
        m = Module()
        a = Signal()
        b = Signal()
        m.d.comb += a.eq(~(b == b))
        with warnings.catch_warnings(record=True) as warns:
            Simulator(m).run()
            self.assertEqual(warns, [])

    def test_large_expr_parser_overflow(self):
        m = Module()
        a = Signal()

        op = a
        for _ in range(50):
            op = (op ^ 1)

        op = op & op

        m.d.comb += a.eq(op)
        Simulator(m)


class SimulatorRegressionTestCase(FHDLTestCase):
    def test_bug_325(self):
        dut = Module()
        dut.d.comb += Signal().eq(Cat())
        Simulator(dut).run()

    def test_bug_473(self):
        sim = Simulator(Module())
        def process():
            self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1)
        sim.add_process(process)
        sim.run()

    def test_bug_595(self):
        dut = Module()
        with dut.FSM(name="name with space"):
            with dut.State(0):
                pass
        sim = Simulator(dut)
        with self.assertRaisesRegex(NameError,
                r"^Signal 'bench\.top\.name with space_state' contains a whitespace character$"):
            with open(os.path.devnull, "w") as f:
                with sim.write_vcd(f):
                    sim.run()

    def test_bug_588(self):
        dut = Module()
        a = Signal(32)
        b = Signal(32)
        z = Signal(32)
        dut.d.comb += z.eq(a << b)
        with self.assertRaisesRegex(OverflowError,
                r"^Value defined at .+?[\\/]test_sim\.py:\d+ is 4294967327 bits wide, "
                r"which is unlikely to simulate in reasonable time$"):
            Simulator(dut)

    def test_bug_566(self):
        dut = Module()
        dut.d.sync += Signal().eq(0)
        sim = Simulator(dut)
        with self.assertWarnsRegex(UserWarning,
                r"^Adding a clock process that drives a clock domain object named 'sync', "
                r"which is distinct from an identically named domain in the simulated design$"):
            sim.add_clock(1e-6, domain=ClockDomain("sync"))

    def test_bug_826(self):
        sim = Simulator(Module())
        def process():
            self.assertEqual((yield C(0b0000, 4) | ~C(1, 1)), 0b0000)
            self.assertEqual((yield C(0b1111, 4) & ~C(1, 1)), 0b0000)
            self.assertEqual((yield C(0b1111, 4) ^ ~C(1, 1)), 0b1111)
        sim.add_process(process)
        sim.run()


# tests/test_tracer.py

from amaranth.hdl.ast import *
from types import SimpleNamespace

from .utils import *

class TracerTestCase(FHDLTestCase):
    def test_fast(self):
        s1 = Signal()
        self.assertEqual(s1.name, "s1")
        s2 = Signal()
        self.assertEqual(s2.name, "s2")

    def test_name(self):
        class Dummy:
            s1 = Signal()
            self.assertEqual(s1.name, "s1")
            s2 = Signal()
            self.assertEqual(s2.name, "s2")

    def test_attr(self):
        ns = SimpleNamespace()
        ns.s1 = Signal()
        self.assertEqual(ns.s1.name, "s1")
        ns.s2 = Signal()
        self.assertEqual(ns.s2.name, "s2")

    def test_index(self):
        l = [None]
        l[0] = Signal()
        self.assertEqual(l[0].name, "$signal")

    def test_deref_cell(self):
        s1 = Signal()
        self.assertEqual(s1.name, "s1")
        s2 = Signal()
        self.assertEqual(s2.name, "s2")

        def dummy():
            return s1, s2

    def test_deref_free(self):
        def inner():
            nonlocal s3, s4
            s3 = Signal()
            s4 = Signal()
            return s1, s2

        s1 = Signal()
        s2 = Signal()
        s3 = None
        s4 = None
        inner()
        self.assertEqual(s1.name, "s1")
        self.assertEqual(s2.name, "s2")
        self.assertEqual(s3.name, "s3")
        self.assertEqual(s4.name, "s4")

    def test_long(self):
        test = ""
        for i in range(100000):
            test += f"dummy{i} = None\n"
        test += "s1 = Signal()\n"
        test += "s2 = Signal()\n"
        ns = {"Signal": Signal}
        exec(test, ns)
        self.assertEqual(ns["s1"].name, "s1")
        self.assertEqual(ns["s2"].name, "s2")

    def test_deref_fast(self):
        def inner(s2):
            s1 = Signal()
            s2 = Signal()
            self.assertEqual(s1.name, "s1")
            self.assertEqual(s2.name, "s2")

            def dummy():
                return s1, s2

        inner(None)


# tests/utils.py

import os
import re
import shutil
import subprocess
import textwrap
import traceback
import unittest

from amaranth.hdl.ast import *
from amaranth.hdl.ir import *
from amaranth.back import rtlil
from amaranth._toolchain import require_tool


__all__ = ["FHDLTestCase"]


class FHDLTestCase(unittest.TestCase):
    def assertRepr(self, obj, repr_str):
        if isinstance(obj, list):
            obj = Statement.cast(obj)
        def prepare_repr(repr_str):
            repr_str = re.sub(r"\s+",   " ",  repr_str)
            repr_str = re.sub(r"\( (?=\()", "(", repr_str)
            repr_str = re.sub(r"\) (?=\))", ")", repr_str)
            return repr_str.strip()
        self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str))

    def assertFormal(self, spec, mode="bmc", depth=1):
        stack = traceback.extract_stack()
        for frame in reversed(stack):
            if os.path.dirname(__file__) not in frame.filename:
                break
            caller = frame

        spec_root, _ = os.path.splitext(caller.filename)
        spec_dir = os.path.dirname(spec_root)
        spec_name = "{}_{}".format(
            os.path.basename(spec_root).replace("test_", "spec_"),
            caller.name.replace("test_", "")
        )

        # The sby -f switch seems not fully functional when sby is reading from stdin.
        if os.path.exists(os.path.join(spec_dir, spec_name)):
            shutil.rmtree(os.path.join(spec_dir, spec_name))

        if mode == "hybrid":
            # A mix of BMC and k-induction, as per personal communication with Claire Wolf.
            script = "setattr -unset init w:* a:amaranth.sample_reg %d"
            mode   = "bmc"
        else:
            script = ""

        config = textwrap.dedent("""\
        [options]
        mode {mode}
        depth {depth}
        wait on
        multiclock on

        [engines]
        smtbmc

        [script]
        read_ilang top.il
        prep
        {script}

        [file top.il]
        {rtlil}
        """).format(
            mode=mode,
            depth=depth,
            script=script,
            rtlil=rtlil.convert_fragment(Fragment.get(spec, platform="formal").prepare())[0]
        )
        with subprocess.Popen(
                [require_tool("sby"), "-f", "-d", spec_name],
                cwd=spec_dir, env={**os.environ, "PYTHONWARNINGS":"ignore"},
                universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
            stdout, stderr = proc.communicate(config)
            if proc.returncode != 0:
                self.fail("Formal verification failed:\n" + stdout)


# amaranth/_toolchain/__init__.py

import os
import shutil


__all__ = ["ToolNotFound", "tool_env_var", "has_tool", "require_tool"]


class ToolNotFound(Exception):
    pass


def tool_env_var(name):
    return name.upper().replace("-", "_").replace("+", "X")


def _get_tool(name):
    return os.environ.get(tool_env_var(name), name)


def has_tool(name):
    return shutil.which(_get_tool(name)) is not None


def require_tool(name):
    env_var = tool_env_var(name)
    path = _get_tool(name)
    if shutil.which(path) is None:
        if env_var in os.environ:
            raise ToolNotFound("Could not find required tool {} in {} as "
                               "specified via the {} environment variable".
                               format(name, path, env_var))
        else:
            raise ToolNotFound("Could not find required tool {} in PATH. Place "
                               "it directly in PATH or specify path explicitly "
                               "via the {} environment variable".
                               format(name, env_var))
    return path


# amaranth/_toolchain/yosys.py

import os
import sys
import re
import subprocess
import warnings
import pathlib
from importlib import metadata as importlib_metadata
try:
    from importlib import resources as importlib_resources
    try:
        importlib_resources.files # py3.9+ stdlib
    except AttributeError:
        import importlib_resources # py3.8- shim
except ImportError:
    importlib_resources = None

from . import has_tool, require_tool


__all__ = ["YosysError", "YosysBinary", "find_yosys"]


class YosysError(Exception):
    pass


class YosysWarning(Warning):
    pass


class YosysBinary:
    @classmethod
    def available(cls):
        """Check for Yosys availability.

        Returns
        -------
        available : bool
            ``True`` if Yosys is installed, ``False`` otherwise. Installed binary may still not
            be runnable, or might be too old to be useful.
        """
        raise NotImplementedError

    @classmethod
    def version(cls):
        """Get Yosys version.

        Returns
        -------
        ``None`` if version number could not be determined, or a 3-tuple ``(major, minor, distance)`` if it could.

        major : int
            Major version.
        minor : int
            Minor version.
        distance : int
            Distance to last tag per ``git describe``. May not be exact for system Yosys.
        """
        raise NotImplementedError

    @classmethod
    def data_dir(cls):
        """Get Yosys data directory.

        Returns
        -------
        data_dir : pathlib.Path
            Yosys data directory (also known as "datdir").
        """
        raise NotImplementedError

    @classmethod
    def run(cls, args, stdin=""):
        """Run Yosys process.

        Parameters
        ----------
        args : list of str
            Arguments, not including the program name.
        stdin : str
            Standard input.

        Returns
        -------
        stdout : str
            Standard output.

        Exceptions
        ----------
        YosysError
            Raised if Yosys returns a non-zero code. The exception message is the standard error
            output.
        """
        raise NotImplementedError

    @classmethod
    def _process_result(cls, returncode, stdout, stderr, ignore_warnings, src_loc_at):
        if returncode:
            raise YosysError(stderr.strip())
        if not ignore_warnings:
            for match in re.finditer(r"(?ms:^Warning: (.+)\n$)", stderr):
                message = match.group(1).replace("\n", " ")
                warnings.warn(message, YosysWarning, stacklevel=3 + src_loc_at)
        return stdout


class _BuiltinYosys(YosysBinary):
    YOSYS_PACKAGE = "amaranth_yosys"

    @classmethod
    def available(cls):
        if importlib_metadata is None or importlib_resources is None:
            return False
        try:
            importlib_metadata.version(cls.YOSYS_PACKAGE)
            return True
        except importlib_metadata.PackageNotFoundError:
            return False

    @classmethod
    def version(cls):
        version = importlib_metadata.version(cls.YOSYS_PACKAGE)
        match = re.match(r"^(\d+)\.(\d+)(?:\.post(\d+))?", version)
        return (int(match[1]), int(match[2]), int(match[3] or 0))

    @classmethod
    def data_dir(cls):
        return importlib_resources.files(cls.YOSYS_PACKAGE) / "share"

    @classmethod
    def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0):
        popen = subprocess.Popen([sys.executable, "-m", cls.YOSYS_PACKAGE, *args],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
            encoding="utf-8")
        stdout, stderr = popen.communicate(stdin)
        return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at)


class _SystemYosys(YosysBinary):
    YOSYS_BINARY = "yosys"

    @classmethod
    def available(cls):
        return has_tool(cls.YOSYS_BINARY)

    @classmethod
    def version(cls):
        version = cls.run(["-V"])
        match = re.match(r"^Yosys (\d+)\.(\d+)(?:\+(\d+))?", version)
        if match:
            return (int(match[1]), int(match[2]), int(match[3] or 0))
        else:
            return None

    @classmethod
    def data_dir(cls):
        popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY) + "-config", "--datdir"],
            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
            encoding="utf-8")
        stdout, stderr = popen.communicate()
        if popen.returncode:
            raise YosysError(stderr.strip())
        return pathlib.Path(stdout.strip())

    @classmethod
    def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0):
        popen = subprocess.Popen([require_tool(cls.YOSYS_BINARY), *args],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
            encoding="utf-8")
        stdout, stderr = popen.communicate(stdin)
        # If Yosys is built with an evaluation version of Verific, then Verific license
        # information is printed first. It consists of empty lines and lines starting with `--`,
        # which are not normally a part of Yosys output, and can be fairly safely removed.
        #
        # This is not ideal, but Verific license conditions rule out any other solution.
        stdout = re.sub(r"\A(-- .+\n|\n)*", "", stdout)
        return cls._process_result(popen.returncode, stdout, stderr, ignore_warnings, src_loc_at)


def find_yosys(requirement):
    """Find an available Yosys executable of required version.

    Parameters
    ----------
    requirement : function
        Version check. Should return ``True`` if the version is acceptable, ``False`` otherwise.

    Returns
    -------
    yosys_binary : subclass of YosysBinary
        Proxy for running the requested version of Yosys.

    Exceptions
    ----------
    YosysError
        Raised if required Yosys version is not found.
    """
    proxies = []
    clauses = os.environ.get("AMARANTH_USE_YOSYS", "system,builtin").split(",")
    for clause in clauses:
        if clause == "builtin":
            proxies.append(_BuiltinYosys)
        elif clause == "system":
            proxies.append(_SystemYosys)
        else:
            raise YosysError("The AMARANTH_USE_YOSYS environment variable contains "
                             "an unrecognized clause {!r}"
                             .format(clause))
    for proxy in proxies:
        if proxy.available():
            version = proxy.version()
            if version is not None and requirement(version):
                return proxy
    else:
        if "AMARANTH_USE_YOSYS" in os.environ:
            raise YosysError("Could not find an acceptable Yosys binary. Searched: {}"
                             .format(", ".join(clauses)))
        else:
            raise YosysError("Could not find an acceptable Yosys binary. The `amaranth-yosys` PyPI "
                             "package, if available for this platform, can be used as fallback")


# amaranth/back/cxxrtl.py

from .._toolchain.yosys import *
from . import rtlil


__all__ = ["YosysError", "convert", "convert_fragment"]


def _convert_rtlil_text(rtlil_text, black_boxes, *, src_loc_at=0):
    if black_boxes is not None:
        if not isinstance(black_boxes, dict):
            raise TypeError("CXXRTL black boxes must be a dictionary, not {!r}"
                            .format(black_boxes))
        for box_name, box_source in black_boxes.items():
            if not isinstance(box_name, str):
                raise TypeError("CXXRTL black box name must be a string, not {!r}"
                                .format(box_name))
            if not isinstance(box_source, str):
                raise TypeError("CXXRTL black box source code must be a string, not {!r}"
                                .format(box_source))

    yosys = find_yosys(lambda ver: ver >= (0, 10))

    script = []
    if black_boxes is not None:
        for box_name, box_source in black_boxes.items():
            script.append("read_ilang <<rtlil\n{}\nrtlil".format(box_source))
    script.append("read_ilang <<rtlil\n{}\nrtlil".format(rtlil_text))
    script.append("write_cxxrtl")

    return yosys.run(["-q", "-"], "\n".join(script), src_loc_at=1 + src_loc_at)


def convert_fragment(*args, black_boxes=None, **kwargs):
    rtlil_text, name_map = rtlil.convert_fragment(*args, **kwargs)
    return _convert_rtlil_text(rtlil_text, black_boxes, src_loc_at=1), name_map


def convert(*args, black_boxes=None, **kwargs):
    rtlil_text = rtlil.convert(*args, **kwargs)
    return _convert_rtlil_text(rtlil_text, black_boxes, src_loc_at=1)


# amaranth/back/rtlil.py

import io
from collections import OrderedDict
from contextlib import contextmanager
import warnings
import re

from .._utils import bits_for, flatten
from ..hdl import ast, ir, mem, xfrm


__all__ = ["convert", "convert_fragment"]


_escape_map = str.maketrans({
    "\"": "\\\"",
    "\\": "\\\\",
    "\t": "\\t",
    "\r": "\\r",
    "\n": "\\n",
})


def _signed(value):
    if isinstance(value, str):
        return False
    elif isinstance(value, int):
        return value < 0
    elif isinstance(value, ast.Const):
        return value.signed
    else:
        assert False, "Invalid constant {!r}".format(value)


def _const(value):
    if isinstance(value, str):
        return "\"{}\"".format(value.translate(_escape_map))
    elif isinstance(value, int):
        if value in range(0, 2**31-1):
            return "{:d}".format(value)
        else:
            # This code path is only used for Instances, where Verilog-like behavior is desirable.
            # Verilog ensures that integers with unspecified width are 32 bits wide or more.
            width = max(32, bits_for(value))
            return _const(ast.Const(value, width))
    elif isinstance(value, ast.Const):
        value_twos_compl = value.value & ((1 << value.width) - 1)
        return "{}'{:0{}b}".format(value.width, value_twos_compl, value.width)
    else:
        assert False, "Invalid constant {!r}".format(value)


class _Namer:
    def __init__(self):
        super().__init__()
        self._anon  = 0
        self._index = 0
        self._names = set()

    def anonymous(self):
        name = "U$${}".format(self._anon)
        assert name not in self._names
        self._anon += 1
        return name

    def _make_name(self, name, local):
        if name is None:
            self._index += 1
            name = "${}".format(self._index)
        elif not local and name[0] not in "\\$":
            name = "\\{}".format(name)
        while name in self._names:
            self._index += 1
            name = "{}${}".format(name, self._index)
        self._names.add(name)
        return name


class _BufferedBuilder:
    def __init__(self):
        super().__init__()
        self._buffer = io.StringIO()

    def __str__(self):
        return self._buffer.getvalue()

    def _append(self, fmt, *args, **kwargs):
        self._buffer.write(fmt.format(*args, **kwargs))


class _ProxiedBuilder:
    def _append(self, *args, **kwargs):
        self.rtlil._append(*args, **kwargs)


class _AttrBuilder:
    def __init__(self, emit_src, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.emit_src = emit_src

    def _attribute(self, name, value, *, indent=0):
        self._append("{}attribute \\{} {}\n",
                     "  " * indent, name, _const(value))

    def _attributes(self, attrs, *, src=None, **kwargs):
        for name, value in attrs.items():
            self._attribute(name, value, **kwargs)
        if src and self.emit_src:
            self._attribute("src", src, **kwargs)


class _Builder(_BufferedBuilder, _Namer):
    def __init__(self, emit_src):
        super().__init__()
        self.emit_src = emit_src

    def module(self, name=None, attrs={}):
        name = self._make_name(name, local=False)
        return _ModuleBuilder(self, name, attrs)


class _ModuleBuilder(_AttrBuilder, _BufferedBuilder, _Namer):
    def __init__(self, rtlil, name, attrs):
        super().__init__(emit_src=rtlil.emit_src)
        self.rtlil = rtlil
        self.name  = name
        self.attrs = {"generator": "Amaranth"}
        self.attrs.update(attrs)

    def __enter__(self):
        self._attributes(self.attrs)
        self._append("module {}\n", self.name)
        return self

    def __exit__(self, *args):
        self._append("end\n")
        self.rtlil._buffer.write(str(self))

    def wire(self, width, port_id=None, port_kind=None, name=None, attrs={}, src=""):
        # Very large wires are unlikely to work. Verilog 1364-2005 requires the limit on vectors
        # to be at least 2**16 bits, and Yosys 0.9 cannot read RTLIL with wires larger than 2**32
        # bits. In practice, wires larger than 2**16 bits, although accepted, cause performance
        # problems without an immediately visible cause, so conservatively limit wire size.
        if width > 2 ** 16:
            raise OverflowError("Wire created at {} is {} bits wide, which is unlikely to "
                                "synthesize correctly"
                                .format(src or "unknown location", width))

        self._attributes(attrs, src=src, indent=1)
        name = self._make_name(name, local=False)
        if port_id is None:
            self._append("  wire width {} {}\n", width, name)
        else:
            assert port_kind in ("input", "output", "inout")
            # By convention, Yosys ports named $\d+ are positional, so there is no way to use
            # a port with such a name. See amaranth-lang/amaranth#733.
            assert port_id is not None
            self._append("  wire width {} {} {} {}\n", width, port_kind, port_id, name)
        return name

    def connect(self, lhs, rhs):
        self._append("  connect {} {}\n", lhs, rhs)

    def memory(self, width, size, name=None, attrs={}, src=""):
        self._attributes(attrs, src=src, indent=1)
        name = self._make_name(name, local=False)
        self._append("  memory width {} size {} {}\n", width, size, name)
        return name

    def cell(self, kind, name=None, params={}, ports={}, attrs={}, src=""):
        self._attributes(attrs, src=src, indent=1)
        name = self._make_name(name, local=False)
        self._append("  cell {} {}\n", kind, name)
        for param, value in params.items():
            if isinstance(value, float):
                self._append("    parameter real \\{} \"{!r}\"\n",
                             param, value)
            elif _signed(value):
                self._append("    parameter signed \\{} {}\n",
                             param, _const(value))
            else:
                self._append("    parameter \\{} {}\n",
                             param, _const(value))
        for port, wire in ports.items():
            # By convention, Yosys ports named $\d+ are positional. Amaranth does not support
            # connecting cell ports by position. See amaranth-lang/amaranth#733.
            assert not re.match(r"^\$\d+$", port)
            self._append("    connect {} {}\n", port, wire)
        self._append("  end\n")
        return name

    def process(self, name=None, attrs={}, src=""):
        name = self._make_name(name, local=True)
        return _ProcessBuilder(self, name, attrs, src)


class _ProcessBuilder(_AttrBuilder, _BufferedBuilder):
    def __init__(self, rtlil, name, attrs, src):
        super().__init__(emit_src=rtlil.emit_src)
        self.rtlil = rtlil
        self.name  = name
        self.attrs = {}
        self.src   = src

    def __enter__(self):
        self._attributes(self.attrs, src=self.src, indent=1)
        self._append("  process {}\n", self.name)
        return self

    def __exit__(self, *args):
        self._append("  end\n")
        self.rtlil._buffer.write(str(self))

    def case(self):
        return _CaseBuilder(self, indent=2)

    def sync(self, kind, cond=None):
        return _SyncBuilder(self, kind, cond)


class _CaseBuilder(_ProxiedBuilder):
    def __init__(self, rtlil, indent):
        self.rtlil  = rtlil
        self.indent = indent

    def __enter__(self):
        return self

    def __exit__(self, *args):
        pass

    def assign(self, lhs, rhs):
        self._append("{}assign {} {}\n", "  " * self.indent, lhs, rhs)

    def switch(self, cond, attrs={}, src=""):
        return _SwitchBuilder(self.rtlil, cond, attrs, src, self.indent)


class _SwitchBuilder(_AttrBuilder, _ProxiedBuilder):
    def __init__(self, rtlil, cond, attrs, src, indent):
        super().__init__(emit_src=rtlil.emit_src)
        self.rtlil  = rtlil
        self.cond   = cond
        self.attrs  = attrs
        self.src    = src
        self.indent = indent

    def __enter__(self):
        self._attributes(self.attrs, src=self.src, indent=self.indent)
        self._append("{}switch {}\n", "  " * self.indent, self.cond)
        return self

    def __exit__(self, *args):
        self._append("{}end\n", "  " * self.indent)

    def case(self, *values, attrs={}, src=""):
        self._attributes(attrs, src=src, indent=self.indent + 1)
        if values == ():
            self._append("{}case\n", "  " * (self.indent + 1))
        else:
            self._append("{}case {}\n", "  " * (self.indent + 1),
                         ", ".join("{}'{}".format(len(value), value) for value in values))
        return _CaseBuilder(self.rtlil, self.indent + 2)


class _SyncBuilder(_ProxiedBuilder):
    def __init__(self, rtlil, kind, cond):
        self.rtlil = rtlil
        self.kind  = kind
        self.cond  = cond

    def __enter__(self):
        if self.cond is None:
            self._append("    sync {}\n", self.kind)
        else:
            self._append("    sync {} {}\n", self.kind, self.cond)
        return self

    def __exit__(self, *args):
        pass

    def update(self, lhs, rhs):
        self._append("      update {} {}\n", lhs, rhs)


def _src(src_loc):
    if src_loc is None:
        return None
    file, line = src_loc
    return "{}:{}".format(file, line)


class _LegalizeValue(Exception):
    def __init__(self, value, branches, src_loc):
        self.value    = value
        self.branches = list(branches)
        self.src_loc  = src_loc


class _ValueCompilerState:
    def __init__(self, rtlil):
        self.rtlil  = rtlil
        self.wires  = ast.SignalDict()
        self.driven = ast.SignalDict()
        self.ports  = ast.SignalDict()
        self.anys   = ast.ValueDict()

        self.expansions = ast.ValueDict()

    def add_driven(self, signal, sync):
        self.driven[signal] = sync

    def add_port(self, signal, kind):
        assert kind in ("i", "o", "io")
        if kind == "i":
            kind = "input"
        elif kind == "o":
            kind = "output"
        elif kind == "io":
            kind = "inout"
        self.ports[signal] = (len(self.ports), kind)

    def resolve(self, signal, prefix=None):
        if len(signal) == 0:
            return "{ }", "{ }"

        if signal in self.wires:
            return self.wires[signal]

        if signal in self.ports:
            port_id, port_kind = self.ports[signal]
        else:
            port_id = port_kind = None
        if prefix is not None:
            wire_name = "{}_{}".format(prefix, signal.name)
        else:
            wire_name = signal.name

        is_sync_driven = signal in self.driven and self.driven[signal]
        
        attrs = dict(signal.attrs)
        if signal._enum_class is not None:
            attrs["enum_base_type"] = signal._enum_class.__name__
            for value in signal._enum_class:
                attrs["enum_value_{:0{}b}".format(value.value, signal.width)] = value.name

        # For every signal in the sync domain, assign \sig's initial value (using the \init reg
        # attribute) to the reset value.
        if is_sync_driven:
            attrs["init"] = ast.Const(signal.reset, signal.width)

        wire_curr = self.rtlil.wire(width=signal.width, name=wire_name,
                                    port_id=port_id, port_kind=port_kind,
                                    attrs=attrs, src=_src(signal.src_loc))
        if is_sync_driven:
            wire_next = self.rtlil.wire(width=signal.width, name=wire_curr + "$next",
                                        src=_src(signal.src_loc))
        else:
            wire_next = None
        self.wires[signal] = (wire_curr, wire_next)

        return wire_curr, wire_next

    def resolve_curr(self, signal, prefix=None):
        wire_curr, wire_next = self.resolve(signal, prefix)
        return wire_curr

    def expand(self, value):
        if not self.expansions:
            return value
        return self.expansions.get(value, value)

    @contextmanager
    def expand_to(self, value, expansion):
        try:
            assert value not in self.expansions
            self.expansions[value] = expansion
            yield
        finally:
            del self.expansions[value]


class _ValueCompiler(xfrm.ValueVisitor):
    def __init__(self, state):
        self.s = state

    def on_unknown(self, value):
        if value is None:
            return None
        else:
            super().on_unknown(value)

    def on_ClockSignal(self, value):
        raise NotImplementedError # :nocov:

    def on_ResetSignal(self, value):
        raise NotImplementedError # :nocov:

    def on_Sample(self, value):
        raise NotImplementedError # :nocov:

    def on_Initial(self, value):
        raise NotImplementedError # :nocov:

    def on_Cat(self, value):
        return "{{ {} }}".format(" ".join(reversed([self(o) for o in value.parts])))

    def _prepare_value_for_Slice(self, value):
        raise NotImplementedError # :nocov:

    def on_Slice(self, value):
        if value.start == 0 and value.stop == len(value.value):
            return self(value.value)

        sigspec = self._prepare_value_for_Slice(value.value)
        if value.start == value.stop:
            return "{}"
        elif value.start + 1 == value.stop:
            return "{} [{}]".format(sigspec, value.start)
        else:
            return "{} [{}:{}]".format(sigspec, value.stop - 1, value.start)

    def on_ArrayProxy(self, value):
        index = self.s.expand(value.index)
        if isinstance(index, ast.Const):
            if index.value < len(value.elems):
                elem = value.elems[index.value]
            else:
                elem = value.elems[-1]
            return self.match_shape(elem, value.shape())
        else:
            max_index = 1 << len(value.index)
            max_elem  = len(value.elems)
            raise _LegalizeValue(value.index, range(min(max_index, max_elem)), value.src_loc)


class _RHSValueCompiler(_ValueCompiler):
    operator_map = {
        (1, "~"):    "$not",
        (1, "-"):    "$neg",
        (1, "b"):    "$reduce_bool",
        (1, "r|"):   "$reduce_or",
        (1, "r&"):   "$reduce_and",
        (1, "r^"):   "$reduce_xor",
        (2, "+"):    "$add",
        (2, "-"):    "$sub",
        (2, "*"):    "$mul",
        (2, "//"):   "$divfloor",
        (2, "%"):    "$modfloor",
        (2, "**"):   "$pow",
        (2, "<<"):   "$sshl",
        (2, ">>"):   "$sshr",
        (2, "&"):    "$and",
        (2, "^"):    "$xor",
        (2, "|"):    "$or",
        (2, "=="):   "$eq",
        (2, "!="):   "$ne",
        (2, "<"):    "$lt",
        (2, "<="):   "$le",
        (2, ">"):    "$gt",
        (2, ">="):   "$ge",
        (3, "m"):    "$mux",
    }

    def on_value(self, value):
        return super().on_value(self.s.expand(value))

    def on_Const(self, value):
        return _const(value)

    def on_AnyConst(self, value):
        if value in self.s.anys:
            return self.s.anys[value]

        res_shape = value.shape()
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell("$anyconst", ports={
            "\\Y": res,
        }, params={
            "WIDTH": res_shape.width,
        }, src=_src(value.src_loc))
        self.s.anys[value] = res
        return res

    def on_AnySeq(self, value):
        if value in self.s.anys:
            return self.s.anys[value]

        res_shape = value.shape()
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell("$anyseq", ports={
            "\\Y": res,
        }, params={
            "WIDTH": res_shape.width,
        }, src=_src(value.src_loc))
        self.s.anys[value] = res
        return res

    def on_Signal(self, value):
        wire_curr, wire_next = self.s.resolve(value)
        return wire_curr

    def on_Operator_unary(self, value):
        arg, = value.operands
        if value.operator in ("u", "s"):
            # These operators don't change the bit pattern, only its interpretation.
            return self(arg)

        arg_shape, res_shape = arg.shape(), value.shape()
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell(self.operator_map[(1, value.operator)], ports={
            "\\A": self(arg),
            "\\Y": res,
        }, params={
            "A_SIGNED": arg_shape.signed,
            "A_WIDTH":  arg_shape.width,
            "Y_WIDTH":  res_shape.width,
        }, src=_src(value.src_loc))
        return res

    def match_shape(self, value, new_shape):
        if isinstance(value, ast.Const):
            return self(ast.Const(value.value, new_shape))

        value_shape = value.shape()
        if new_shape.width <= value_shape.width:
            return self(ast.Slice(value, 0, new_shape.width))

        res = self.s.rtlil.wire(width=new_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell("$pos", ports={
            "\\A": self(value),
            "\\Y": res,
        }, params={
            "A_SIGNED": value_shape.signed,
            "A_WIDTH":  value_shape.width,
            "Y_WIDTH":  new_shape.width,
        }, src=_src(value.src_loc))
        return res

    def on_Operator_binary(self, value):
        lhs, rhs = value.operands
        lhs_shape, rhs_shape, res_shape = lhs.shape(), rhs.shape(), value.shape()
        if lhs_shape.signed == rhs_shape.signed or value.operator in ("<<", ">>", "**"):
            lhs_wire = self(lhs)
            rhs_wire = self(rhs)
        else:
            lhs_shape = rhs_shape = ast.signed(max(lhs_shape.width + rhs_shape.signed,
                                                   rhs_shape.width + lhs_shape.signed))
            lhs_wire = self.match_shape(lhs, lhs_shape)
            rhs_wire = self.match_shape(rhs, rhs_shape)
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell(self.operator_map[(2, value.operator)], ports={
            "\\A": lhs_wire,
            "\\B": rhs_wire,
            "\\Y": res,
        }, params={
            "A_SIGNED": lhs_shape.signed,
            "A_WIDTH":  lhs_shape.width,
            "B_SIGNED": rhs_shape.signed,
            "B_WIDTH":  rhs_shape.width,
            "Y_WIDTH":  res_shape.width,
        }, src=_src(value.src_loc))
        if value.operator in ("//", "%"):
            # RTLIL leaves division by zero undefined, but we require it to return zero.
            divmod_res = res
            res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
            self.s.rtlil.cell("$mux", ports={
                "\\A": divmod_res,
                "\\B": self(ast.Const(0, res_shape)),
                "\\S": self(rhs == 0),
                "\\Y": res,
            }, params={
                "WIDTH": res_shape.width
            }, src=_src(value.src_loc))
        return res

    def on_Operator_mux(self, value):
        sel, val1, val0 = value.operands
        if len(sel) != 1:
            sel = sel.bool()
        res_shape = value.shape()
        val1_wire = self.match_shape(val1, res_shape)
        val0_wire = self.match_shape(val0, res_shape)
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        self.s.rtlil.cell("$mux", ports={
            "\\A": val0_wire,
            "\\B": val1_wire,
            "\\S": self(sel),
            "\\Y": res,
        }, params={
            "WIDTH": res_shape.width
        }, src=_src(value.src_loc))
        return res

    def on_Operator(self, value):
        if len(value.operands) == 1:
            return self.on_Operator_unary(value)
        elif len(value.operands) == 2:
            return self.on_Operator_binary(value)
        elif len(value.operands) == 3:
            assert value.operator == "m"
            return self.on_Operator_mux(value)
        else:
            raise TypeError # :nocov:

    def _prepare_value_for_Slice(self, value):
        if isinstance(value, (ast.Signal, ast.Slice, ast.Cat)):
            sigspec = self(value)
        else:
            sigspec = self.s.rtlil.wire(len(value), src=_src(value.src_loc))
            self.s.rtlil.connect(sigspec, self(value))
        return sigspec

    def on_Part(self, value):
        lhs, rhs = value.value, value.offset
        if value.stride != 1:
            rhs *= value.stride
        lhs_shape, rhs_shape, res_shape = lhs.shape(), rhs.shape(), value.shape()
        res = self.s.rtlil.wire(width=res_shape.width, src=_src(value.src_loc))
        # Note: Verilog's x[o+:w] construct produces a $shiftx cell, not a $shift cell.
        # However, Amaranth's semantics defines the out-of-range bits to be zero, so it is correct
        # to use a $shift cell here instead, even though it produces less idiomatic Verilog.
        self.s.rtlil.cell("$shift", ports={
            "\\A": self(lhs),
            "\\B": self(rhs),
            "\\Y": res,
        }, params={
            "A_SIGNED": lhs_shape.signed,
            "A_WIDTH":  lhs_shape.width,
            "B_SIGNED": rhs_shape.signed,
            "B_WIDTH":  rhs_shape.width,
            "Y_WIDTH":  res_shape.width,
        }, src=_src(value.src_loc))
        return res


class _LHSValueCompiler(_ValueCompiler):
    def on_Const(self, value):
        raise TypeError # :nocov:

    def on_AnyConst(self, value):
        raise TypeError # :nocov:

    def on_AnySeq(self, value):
        raise TypeError # :nocov:

    def on_Operator(self, value):
        if value.operator in ("u", "s"):
            # These operators are transparent on the LHS.
            arg, = value.operands
            return self(arg)

        raise TypeError # :nocov:

    def match_shape(self, value, new_shape):
        value_shape = value.shape()
        if new_shape.width == value_shape.width:
            return self(value)
        elif new_shape.width < value_shape.width:
            return self(ast.Slice(value, 0, new_shape.width))
        else: # new_shape.width > value_shape.width
            dummy_bits = new_shape.width - value_shape.width
            dummy_wire = self.s.rtlil.wire(dummy_bits)
            return "{{ {} {} }}".format(dummy_wire, self(value))

    def on_Signal(self, value):
        if value not in self.s.driven:
            raise ValueError("No LHS wire for non-driven signal {}".format(repr(value)))
        wire_curr, wire_next = self.s.resolve(value)
        return wire_next or wire_curr

    def _prepare_value_for_Slice(self, value):
        assert isinstance(value, (ast.Signal, ast.Slice, ast.Cat, ast.Part))
        return self(value)

    def on_Part(self, value):
        offset = self.s.expand(value.offset)
        if isinstance(offset, ast.Const):
            start = offset.value * value.stride
            stop  = start + value.width
            slice = self(ast.Slice(value.value, start, min(len(value.value), stop)))
            if len(value.value) >= stop:
                return slice
            else:
                dummy_wire = self.s.rtlil.wire(stop - len(value.value))
                return "{{ {} {} }}".format(dummy_wire, slice)
        else:
            # Only so many possible parts. The amount of branches is exponential; if value.offset
            # is large (e.g. 32-bit wide), trying to naively legalize it is likely to exhaust
            # system resources.
            max_branches = len(value.value) // value.stride + 1
            raise _LegalizeValue(value.offset,
                                 range(1 << len(value.offset))[:max_branches],
                                 value.src_loc)


class _StatementCompiler(xfrm.StatementVisitor):
    def __init__(self, state, rhs_compiler, lhs_compiler):
        self.state        = state
        self.rhs_compiler = rhs_compiler
        self.lhs_compiler = lhs_compiler

        self._case        = None
        self._test_cache  = {}
        self._has_rhs     = False
        self._wrap_assign = False

    @contextmanager
    def case(self, switch, values, attrs={}, src=""):
        try:
            old_case = self._case
            with switch.case(*values, attrs=attrs, src=src) as self._case:
                yield
        finally:
            self._case = old_case

    def _check_rhs(self, value):
        if self._has_rhs or next(iter(value._rhs_signals()), None) is not None:
            self._has_rhs = True

    def on_Assign(self, stmt):
        self._check_rhs(stmt.rhs)

        lhs_shape, rhs_shape = stmt.lhs.shape(), stmt.rhs.shape()
        if lhs_shape.width == rhs_shape.width:
            rhs_sigspec = self.rhs_compiler(stmt.rhs)
        else:
            # In RTLIL, LHS and RHS of assignment must have exactly same width.
            rhs_sigspec = self.rhs_compiler.match_shape(stmt.rhs, lhs_shape)
        if self._wrap_assign:
            # In RTLIL, all assigns are logically sequenced before all switches, even if they are
            # interleaved in the source. In Amaranth, the source ordering is used. To handle this
            # mismatch, we wrap all assigns following a switch in a dummy switch.
            with self._case.switch("{ }") as wrap_switch:
                with wrap_switch.case() as wrap_case:
                    wrap_case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)
        else:
            self._case.assign(self.lhs_compiler(stmt.lhs), rhs_sigspec)

    def on_property(self, stmt):
        self(stmt._check.eq(stmt.test))
        self(stmt._en.eq(1))

        en_wire = self.rhs_compiler(stmt._en)
        check_wire = self.rhs_compiler(stmt._check)
        self.state.rtlil.cell("$" + stmt._kind, ports={
            "\\A": check_wire,
            "\\EN": en_wire,
        }, src=_src(stmt.src_loc), name=stmt.name)

    on_Assert = on_property
    on_Assume = on_property
    on_Cover  = on_property

    def on_Switch(self, stmt):
        self._check_rhs(stmt.test)

        if not self.state.expansions:
            # We repeatedly translate the same switches over and over (see the LHSGroupAnalyzer
            # related code below), and translating the switch test only once helps readability.
            if stmt not in self._test_cache:
                self._test_cache[stmt] = self.rhs_compiler(stmt.test)
            test_sigspec = self._test_cache[stmt]
        else:
            # However, if the switch test contains an illegal value, then it may not be cached
            # (since the illegal value will be repeatedly replaced with different constants), so
            # don't cache anything in that case.
            test_sigspec = self.rhs_compiler(stmt.test)

        with self._case.switch(test_sigspec, src=_src(stmt.src_loc)) as switch:
            for values, stmts in stmt.cases.items():
                case_attrs = {}
                case_src = None
                if values in stmt.case_src_locs:
                    case_src = _src(stmt.case_src_locs[values])
                if isinstance(stmt.test, ast.Signal) and stmt.test.decoder:
                    decoded_values = []
                    for value in values:
                        if "-" in value:
                            decoded_values.append("<multiple>")
                        else:
                            decoded_values.append(stmt.test.decoder(int(value, 2)))
                    case_attrs["amaranth.decoding"] = "|".join(decoded_values)
                with self.case(switch, values, attrs=case_attrs, src=case_src):
                    self._wrap_assign = False
                    self.on_statements(stmts)
        self._wrap_assign = True

    def on_statement(self, stmt):
        try:
            super().on_statement(stmt)
        except _LegalizeValue as legalize:
            with self._case.switch(self.rhs_compiler(legalize.value),
                                   src=_src(legalize.src_loc)) as switch:
                shape = legalize.value.shape()
                tests = ["{:0{}b}".format(v, shape.width) for v in legalize.branches]
                if tests:
                    tests[-1] = "-" * shape.width
                for branch, test in zip(legalize.branches, tests):
                    with self.case(switch, (test,)):
                        self._wrap_assign = False
                        branch_value = ast.Const(branch, shape)
                        with self.state.expand_to(legalize.value, branch_value):
                            self.on_statement(stmt)
            self._wrap_assign = True

    def on_statements(self, stmts):
        for stmt in stmts:
            self.on_statement(stmt)


def _convert_fragment(builder, fragment, name_map, hierarchy):
    if isinstance(fragment, ir.Instance):
        port_map = OrderedDict()
        for port_name, (value, dir) in fragment.named_ports.items():
            port_map["\\{}".format(port_name)] = value

        if fragment.type[0] == "$":
            return fragment.type, port_map
        else:
            return "\\{}".format(fragment.type), port_map

    module_name  = hierarchy[-1] or "anonymous"
    module_attrs = OrderedDict()
    if len(hierarchy) == 1:
        module_attrs["top"] = 1
    module_attrs["amaranth.hierarchy"] = ".".join(name or "anonymous" for name in hierarchy)

    with builder.module(module_name, attrs=module_attrs) as module:
        compiler_state = _ValueCompilerState(module)
        rhs_compiler   = _RHSValueCompiler(compiler_state)
        lhs_compiler   = _LHSValueCompiler(compiler_state)
        stmt_compiler  = _StatementCompiler(compiler_state, rhs_compiler, lhs_compiler)

        # If the fragment is completely empty, add a dummy wire to it, or Yosys will interpret
        # it as a black box by default (when read as Verilog).
        if not fragment.ports and not fragment.statements and not fragment.subfragments:
            module.wire(1, name="$empty_module_filler")

        # Register all signals driven in the current fragment. This must be done first, as it
        # affects further codegen; e.g. whether \sig$next signals will be generated and used.
        for domain, signal in fragment.iter_drivers():
            compiler_state.add_driven(signal, sync=domain is not None)

        # Transform all signals used as ports in the current fragment eagerly and outside of
        # any hierarchy, to make sure they get sensible (non-prefixed) names.
        for signal in fragment.ports:
            compiler_state.add_port(signal, fragment.ports[signal])
            compiler_state.resolve_curr(signal)

        # Transform all clocks clocks and resets eagerly and outside of any hierarchy, to make
        # sure they get sensible (non-prefixed) names. This does not affect semantics.
        for domain, _ in fragment.iter_sync():
            cd = fragment.domains[domain]
            compiler_state.resolve_curr(cd.clk)
            if cd.rst is not None:
                compiler_state.resolve_curr(cd.rst)

        # Transform all subfragments to their respective cells. Transforming signals connected
        # to their ports into wires eagerly makes sure they get sensible (prefixed with submodule
        # name) names.
        memories = OrderedDict()
        for subfragment, sub_name in fragment.subfragments:
            if sub_name is None:
                sub_name = module.anonymous()

            sub_params = OrderedDict()
            if hasattr(subfragment, "parameters"):
                for param_name, param_value in subfragment.parameters.items():
                    if isinstance(param_value, mem.Memory):
                        memory = param_value
                        if memory not in memories:
                            memories[memory] = module.memory(width=memory.width, size=memory.depth,
                                                             name=memory.name, attrs=memory.attrs)
                            addr_bits = bits_for(memory.depth)
                            data_parts = []
                            data_mask = (1 << memory.width) - 1
                            for addr in range(memory.depth):
                                if addr < len(memory.init):
                                    data = memory.init[addr] & data_mask
                                else:
                                    data = 0
                                data_parts.append("{:0{}b}".format(data, memory.width))
                            module.cell("$meminit", ports={
                                "\\ADDR": rhs_compiler(ast.Const(0, addr_bits)),
                                "\\DATA": "{}'".format(memory.width * memory.depth) +
                                          "".join(reversed(data_parts)),
                            }, params={
                                "MEMID": memories[memory],
                                "ABITS": addr_bits,
                                "WIDTH": memory.width,
                                "WORDS": memory.depth,
                                "PRIORITY": 0,
                            })

                        param_value = memories[memory]

                    sub_params[param_name] = param_value

            sub_type, sub_port_map = \
                _convert_fragment(builder, subfragment, name_map,
                                  hierarchy=hierarchy + (sub_name,))

            sub_ports = OrderedDict()
            for port, value in sub_port_map.items():
                if not isinstance(subfragment, ir.Instance):
                    for signal in value._rhs_signals():
                        compiler_state.resolve_curr(signal, prefix=sub_name)
                if len(value) > 0:
                    sub_ports[port] = rhs_compiler(value)

            module.cell(sub_type, name=sub_name, ports=sub_ports, params=sub_params,
                        attrs=subfragment.attrs)

        # If we emit all of our combinatorial logic into a single RTLIL process, Verilog
        # simulators will break horribly, because Yosys write_verilog transforms RTLIL processes
        # into always @* blocks with blocking assignment, and that does not create delta cycles.
        #
        # Therefore, we translate the fragment as many times as there are independent groups
        # of signals (a group is a transitive closure of signals that appear together on LHS),
        # splitting them into many RTLIL (and thus Verilog) processes.
        lhs_grouper = xfrm.LHSGroupAnalyzer()
        lhs_grouper.on_statements(fragment.statements)

        for group, group_signals in lhs_grouper.groups().items():
            lhs_group_filter = xfrm.LHSGroupFilter(group_signals)
            group_stmts = lhs_group_filter(fragment.statements)

            with module.process(name="$group_{}".format(group)) as process:
                with process.case() as case:
                    # For every signal in comb domain, assign \sig$next to the reset value.
                    # For every signal in sync domains, assign \sig$next to the current
                    # value (\sig).
                    for domain, signal in fragment.iter_drivers():
                        if signal not in group_signals:
                            continue
                        if domain is None:
                            prev_value = ast.Const(signal.reset, signal.width)
                        else:
                            prev_value = signal
                        case.assign(lhs_compiler(signal), rhs_compiler(prev_value))

                    # Convert statements into decision trees.
                    stmt_compiler._case = case
                    stmt_compiler._has_rhs = False
                    stmt_compiler._wrap_assign = False
                    stmt_compiler(group_stmts)
        
        # For every driven signal in the sync domain, create a flop of appropriate type. Which type
        # is appropriate depends on the domain: for domains with sync reset, it is a $dff, for
        # domains with async reset it is an $adff. The latter is directly provided with the reset
        # value as a parameter to the cell, which is directly assigned during reset.
        for domain, signal in fragment.iter_sync():
            cd = fragment.domains[domain]

            wire_clk = compiler_state.resolve_curr(cd.clk)
            wire_rst = compiler_state.resolve_curr(cd.rst) if cd.rst is not None else None
            wire_curr, wire_next = compiler_state.resolve(signal)

            if not cd.async_reset:
                # For sync reset flops, the reset value comes from logic inserted by 
                # `hdl.xfrm.DomainLowerer`.
                module.cell("$dff", ports={
                    "\\CLK": wire_clk,
                    "\\D": wire_next,
                    "\\Q": wire_curr
                }, params={
                    "CLK_POLARITY": int(cd.clk_edge == "pos"),
                    "WIDTH": signal.width
                })
            else:
                # For async reset flops, the reset value is provided directly to the cell.
                module.cell("$adff", ports={
                    "\\ARST": wire_rst,
                    "\\CLK": wire_clk,
                    "\\D": wire_next,
                    "\\Q": wire_curr
                }, params={
                    "ARST_POLARITY": ast.Const(1),
                    "ARST_VALUE": ast.Const(signal.reset, signal.width),
                    "CLK_POLARITY": int(cd.clk_edge == "pos"),
                    "WIDTH": signal.width
                })

        # Any signals that are used but neither driven nor connected to an input port always
        # assume their reset values. We need to assign the reset value explicitly, since only
        # driven sync signals are handled by the logic above.
        #
        # Because this assignment is done at a late stage, a single Signal object can get assigned
        # many times, once in each module it is used. This is a deliberate decision; the possible
        # alternatives are to add ports for undriven signals (which requires choosing one module
        # to drive it to reset value arbitrarily) or to replace them with their reset value (which
        # removes valuable source location information).
        driven = ast.SignalSet()
        for domain, signals in fragment.iter_drivers():
            driven.update(flatten(signal._lhs_signals() for signal in signals))
        driven.update(fragment.iter_ports(dir="i"))
        driven.update(fragment.iter_ports(dir="io"))
        for subfragment, sub_name in fragment.subfragments:
            driven.update(subfragment.iter_ports(dir="o"))
            driven.update(subfragment.iter_ports(dir="io"))

        for wire in compiler_state.wires:
            if wire in driven:
                continue
            wire_curr, _ = compiler_state.wires[wire]
            module.connect(wire_curr, rhs_compiler(ast.Const(wire.reset, wire.width)))

    # Collect the names we've given to our ports in RTLIL, and correlate these with the signals
    # represented by these ports. If we are a submodule, this will be necessary to create a cell
    # for us in the parent module.
    port_map = OrderedDict()
    for signal in fragment.ports:
        port_map[compiler_state.resolve_curr(signal)] = signal

    # Finally, collect the names we've given to each wire in RTLIL, and provide these to
    # the caller, to allow manipulating them in the toolchain.
    for signal in compiler_state.wires:
        wire_name = compiler_state.resolve_curr(signal)
        if wire_name.startswith("\\"):
            wire_name = wire_name[1:]
        name_map[signal] = hierarchy + (wire_name,)

    return module.name, port_map


def convert_fragment(fragment, name="top", *, emit_src=True):
    assert isinstance(fragment, ir.Fragment)
    builder = _Builder(emit_src=emit_src)
    name_map = ast.SignalDict()
    _convert_fragment(builder, fragment, name_map, hierarchy=(name,))
    return str(builder), name_map


def convert(elaboratable, name="top", platform=None, *, ports, emit_src=True, **kwargs):
    fragment = ir.Fragment.get(elaboratable, platform).prepare(ports=ports, **kwargs)
    il_text, name_map = convert_fragment(fragment, name, emit_src=emit_src)
    return il_text


# amaranth/back/verilog.py

import warnings

from .._toolchain.yosys import *
from ..hdl import ir
from . import rtlil


__all__ = ["YosysError", "convert", "convert_fragment"]


def _convert_rtlil_text(rtlil_text, *, strip_internal_attrs=False, write_verilog_opts=()):
    # this version requirement needs to be synchronized with the one in pyproject.toml!
    yosys = find_yosys(lambda ver: ver >= (0, 10))
    yosys_version = yosys.version()

    script = []
    script.append("read_ilang <<rtlil\n{}\nrtlil".format(rtlil_text))
    if yosys_version >= (0, 17):
        script.append("proc -nomux -norom")
    else:
        script.append("proc -nomux")
    script.append("memory_collect")

    if strip_internal_attrs:
        attr_map = []
        attr_map.append("-remove generator")
        attr_map.append("-remove top")
        attr_map.append("-remove src")
        attr_map.append("-remove amaranth.hierarchy")
        attr_map.append("-remove amaranth.decoding")
        script.append("attrmap {}".format(" ".join(attr_map)))
        script.append("attrmap -modattr {}".format(" ".join(attr_map)))

    script.append("write_verilog -norename {}".format(" ".join(write_verilog_opts)))

    return yosys.run(["-q", "-"], "\n".join(script),
        # At the moment, Yosys always shows a warning indicating that not all processes can be
        # translated to Verilog. We carefully emit only the processes that *can* be translated, and
        # squash this warning. Once Yosys' write_verilog pass is fixed, we should remove this.
        ignore_warnings=True)


def convert_fragment(*args, strip_internal_attrs=False, **kwargs):
    rtlil_text, name_map = rtlil.convert_fragment(*args, **kwargs)
    return _convert_rtlil_text(rtlil_text, strip_internal_attrs=strip_internal_attrs), name_map


def convert(elaboratable, name="top", platform=None, *, ports, emit_src=True,
            strip_internal_attrs=False, **kwargs):
    fragment = ir.Fragment.get(elaboratable, platform).prepare(ports=ports, **kwargs)
    verilog_text, name_map = convert_fragment(fragment, name, emit_src=emit_src, strip_internal_attrs=strip_internal_attrs)
    return verilog_text


# amaranth/build/__init__.py

from .dsl import *
from .res import ResourceError
from .plat import *


# amaranth/build/dsl.py

from collections import OrderedDict


__all__ = ["Pins", "PinsN", "DiffPairs", "DiffPairsN",
           "Attrs", "Clock", "Subsignal", "Resource", "Connector"]


class Pins:
    def __init__(self, names, *, dir="io", invert=False, conn=None, assert_width=None):
        if not isinstance(names, str):
            raise TypeError("Names must be a whitespace-separated string, not {!r}"
                            .format(names))
        names = names.split()

        if conn is not None:
            conn_name, conn_number = conn
            if not (isinstance(conn_name, str) and isinstance(conn_number, (int, str))):
                raise TypeError("Connector must be None or a pair of string (connector name) and "
                                "integer/string (connector number), not {!r}"
                                .format(conn))
            names = ["{}_{}:{}".format(conn_name, conn_number, name) for name in names]

        if dir not in ("i", "o", "io", "oe"):
            raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not {!r}"
                            .format(dir))

        if assert_width is not None and len(names) != assert_width:
            raise AssertionError("{} names are specified ({}), but {} names are expected"
                                 .format(len(names), " ".join(names), assert_width))

        self.names  = names
        self.dir    = dir
        self.invert = bool(invert)

    def __len__(self):
        return len(self.names)

    def __iter__(self):
        return iter(self.names)

    def map_names(self, mapping, resource):
        mapped_names = []
        for name in self.names:
            while ":" in name:
                if name not in mapping:
                    raise NameError("Resource {!r} refers to nonexistent connector pin {}"
                                    .format(resource, name))
                name = mapping[name]
            mapped_names.append(name)
        return mapped_names

    def __repr__(self):
        return "(pins{} {} {})".format("-n" if self.invert else "",
            self.dir, " ".join(self.names))


def PinsN(*args, **kwargs):
    return Pins(*args, invert=True, **kwargs)


class DiffPairs:
    def __init__(self, p, n, *, dir="io", invert=False, conn=None, assert_width=None):
        self.p = Pins(p, dir=dir, conn=conn, assert_width=assert_width)
        self.n = Pins(n, dir=dir, conn=conn, assert_width=assert_width)

        if len(self.p.names) != len(self.n.names):
            raise TypeError("Positive and negative pins must have the same width, but {!r} "
                            "and {!r} do not"
                            .format(self.p, self.n))

        self.dir    = dir
        self.invert = bool(invert)

    def __len__(self):
        return len(self.p.names)

    def __iter__(self):
        return zip(self.p.names, self.n.names)

    def __repr__(self):
        return "(diffpairs{} {} (p {}) (n {}))".format("-n" if self.invert else "",
            self.dir, " ".join(self.p.names), " ".join(self.n.names))


def DiffPairsN(*args, **kwargs):
    return DiffPairs(*args, invert=True, **kwargs)


class Attrs(OrderedDict):
    def __init__(self, **attrs):
        for key, value in attrs.items():
            if not (value is None or isinstance(value, (str, int)) or hasattr(value, "__call__")):
                raise TypeError("Value of attribute {} must be None, int, str, or callable, "
                                "not {!r}"
                                .format(key, value))

        super().__init__(**attrs)

    def __repr__(self):
        items = []
        for key, value in self.items():
            if value is None:
                items.append("!" + key)
            else:
                items.append(key + "=" + repr(value))
        return "(attrs {})".format(" ".join(items))


class Clock:
    def __init__(self, frequency):
        if not isinstance(frequency, (float, int)):
            raise TypeError("Clock frequency must be a number")

        self.frequency = float(frequency)

    @property
    def period(self):
        return 1 / self.frequency

    def __repr__(self):
        return "(clock {})".format(self.frequency)


class Subsignal:
    def __init__(self, name, *args):
        self.name  = name
        self.ios   = []
        self.attrs = Attrs()
        self.clock = None

        if not args:
            raise ValueError("Missing I/O constraints")
        for arg in args:
            if isinstance(arg, (Pins, DiffPairs)):
                if not self.ios:
                    self.ios.append(arg)
                else:
                    raise TypeError("Pins and DiffPairs are incompatible with other location or "
                                    "subsignal constraints, but {!r} appears after {!r}"
                                    .format(arg, self.ios[-1]))
            elif isinstance(arg, Subsignal):
                if not self.ios or isinstance(self.ios[-1], Subsignal):
                    self.ios.append(arg)
                else:
                    raise TypeError("Subsignal is incompatible with location constraints, but "
                                    "{!r} appears after {!r}"
                                    .format(arg, self.ios[-1]))
            elif isinstance(arg, Attrs):
                self.attrs.update(arg)
            elif isinstance(arg, Clock):
                if self.ios and isinstance(self.ios[-1], (Pins, DiffPairs)):
                    if self.clock is None:
                        self.clock = arg
                    else:
                        raise ValueError("Clock constraint can be applied only once")
                else:
                    raise TypeError("Clock constraint can only be applied to Pins or DiffPairs, "
                                    "not {!r}"
                                    .format(self.ios[-1]))
            else:
                raise TypeError("Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, "
                                "or Clock, not {!r}"
                                .format(arg))

    def _content_repr(self):
        parts = []
        for io in self.ios:
            parts.append(repr(io))
        if self.clock is not None:
            parts.append(repr(self.clock))
        if self.attrs:
            parts.append(repr(self.attrs))
        return " ".join(parts)

    def __repr__(self):
        return "(subsignal {} {})".format(self.name, self._content_repr())


class Resource(Subsignal):
    @classmethod
    def family(cls, name_or_number, number=None, *, ios, default_name, name_suffix=""):
        # This constructor accepts two different forms:
        #  1. Number-only form:
        #       Resource.family(0, default_name="name", ios=[Pins("A0 A1")])
        #  2. Name-and-number (name override) form:
        #       Resource.family("override", 0, default_name="name", ios=...)
        # This makes it easier to build abstractions for resources, e.g. an SPIResource abstraction
        # could simply delegate to `Resource.family(*args, default_name="spi", ios=ios)`.
        # The name_suffix argument is meant to support creating resources with
        # similar names, such as spi_flash, spi_flash_2x, etc.
        if name_suffix:  # Only add "_" if we actually have a suffix.
            name_suffix = "_" + name_suffix

        if number is None: # name_or_number is number
            return cls(default_name + name_suffix, name_or_number, *ios)
        else: # name_or_number is name
            return cls(name_or_number + name_suffix, number, *ios)

    def __init__(self, name, number, *args):
        if not isinstance(number, int):
            raise TypeError("Resource number must be an integer, not {!r}"
                            .format(number))

        super().__init__(name, *args)
        self.number = number

    def __repr__(self):
        return "(resource {} {} {})".format(self.name, self.number, self._content_repr())


class Connector:
    def __init__(self, name, number, io, *, conn=None):
        self.name    = name
        self.number  = number
        mapping = OrderedDict()

        if isinstance(io, dict):
            for conn_pin, plat_pin in io.items():
                if not isinstance(conn_pin, str):
                    raise TypeError("Connector pin name must be a string, not {!r}"
                                    .format(conn_pin))
                if not isinstance(plat_pin, str):
                    raise TypeError("Platform pin name must be a string, not {!r}"
                                    .format(plat_pin))
                mapping[conn_pin] = plat_pin

        elif isinstance(io, str):
            for conn_pin, plat_pin in enumerate(io.split(), start=1):
                if plat_pin == "-":
                    continue

                mapping[str(conn_pin)] = plat_pin
        else:
            raise TypeError("Connector I/Os must be a dictionary or a string, not {!r}"
                            .format(io))

        if conn is not None:
            conn_name, conn_number = conn
            if not (isinstance(conn_name, str) and isinstance(conn_number, (int, str))):
                raise TypeError("Connector must be None or a pair of string (connector name) and "
                                "integer/string (connector number), not {!r}"
                                .format(conn))

            for conn_pin, plat_pin in mapping.items():
                mapping[conn_pin] = "{}_{}:{}".format(conn_name, conn_number, plat_pin)

        self.mapping = mapping

    def __repr__(self):
        return "(connector {} {} {})".format(self.name, self.number,
                                             " ".join("{}=>{}".format(conn, plat)
                                                      for conn, plat in self.mapping.items()))

    def __len__(self):
        return len(self.mapping)

    def __iter__(self):
        for conn_pin, plat_pin in self.mapping.items():
            yield "{}_{}:{}".format(self.name, self.number, conn_pin), plat_pin


# amaranth/build/plat.py

from collections import OrderedDict
from collections.abc import Iterable
from abc import ABCMeta, abstractmethod
import os
import textwrap
import re
import jinja2

from .. import __version__
from .._toolchain import *
from ..hdl import *
from ..hdl.xfrm import SampleLowerer, DomainLowerer
from ..lib.cdc import ResetSynchronizer
from ..back import rtlil, verilog
from .res import *
from .run import *


__all__ = ["Platform", "TemplatedPlatform"]


class Platform(ResourceManager, metaclass=ABCMeta):
    resources      = property(abstractmethod(lambda: None))
    connectors     = property(abstractmethod(lambda: None))
    default_clk    = None
    default_rst    = None
    required_tools = property(abstractmethod(lambda: None))

    def __init__(self):
        super().__init__(self.resources, self.connectors)

        self.extra_files = OrderedDict()

        self._prepared   = False

    @property
    def default_clk_constraint(self):
        if self.default_clk is None:
            raise AttributeError("Platform '{}' does not define a default clock"
                                 .format(type(self).__name__))
        return self.lookup(self.default_clk).clock

    @property
    def default_clk_frequency(self):
        constraint = self.default_clk_constraint
        if constraint is None:
            raise AttributeError("Platform '{}' does not constrain its default clock"
                                 .format(type(self).__name__))
        return constraint.frequency

    def add_file(self, filename, content):
        if not isinstance(filename, str):
            raise TypeError("File name must be a string, not {!r}"
                            .format(filename))
        if hasattr(content, "read"):
            content = content.read()
        elif not isinstance(content, (str, bytes)):
            raise TypeError("File contents must be str, bytes, or a file-like object, not {!r}"
                            .format(content))
        if filename in self.extra_files:
            if self.extra_files[filename] != content:
                raise ValueError("File {!r} already exists"
                                 .format(filename))
        else:
            self.extra_files[filename] = content

    def iter_files(self, *suffixes):
        for filename in self.extra_files:
            if filename.endswith(suffixes):
                yield filename

    @property
    def _toolchain_env_var(self):
        return f"AMARANTH_ENV_{tool_env_var(self.toolchain)}"

    # TODO(amaranth-0.5): remove
    @property
    def _all_toolchain_env_vars(self):
        return (
            f"AMARANTH_ENV_{self.toolchain.replace('-', '_').replace('+', 'X')}",
            self._toolchain_env_var,
        )

    def build(self, elaboratable, name="top",
              build_dir="build", do_build=True,
              program_opts=None, do_program=False,
              **kwargs):
        # The following code performs a best-effort check for presence of required tools upfront,
        # before performing any build actions, to provide a better diagnostic. It does not handle
        # several corner cases:
        #  1. `require_tool` does not source toolchain environment scripts, so if such a script
        #     is used, the check is skipped, and `execute_local()` may fail;
        #  2. if the design is not built (do_build=False), most of the tools are not required and
        #     in fact might not be available if the design will be built manually with a different
        #     environment script specified, or on a different machine; however, Yosys is required
        #     by virtually every platform anyway, to provide debug Verilog output, and `prepare()`
        #     may fail.
        # This is OK because even if `require_tool` succeeds, the toolchain might be broken anyway.
        # The check only serves to catch common errors earlier.
        got_env_var = any(v in os.environ for v in self._all_toolchain_env_vars)
        if do_build and not got_env_var:
            for tool in self.required_tools:
                require_tool(tool)

        plan = self.prepare(elaboratable, name, **kwargs)
        if not do_build:
            return plan

        products = plan.execute_local(build_dir)
        if not do_program:
            return products

        self.toolchain_program(products, name, **(program_opts or {}))

    def has_required_tools(self):
        if any(v in os.environ for v in self._all_toolchain_env_vars):
            return True
        return all(has_tool(name) for name in self.required_tools)

    def create_missing_domain(self, name):
        # Simple instantiation of a clock domain driven directly by the board clock and reset.
        # This implementation uses a single ResetSynchronizer to ensure that:
        #   * an external reset is definitely synchronized to the system clock;
        #   * release of power-on reset, which is inherently asynchronous, is synchronized to
        #     the system clock.
        # Many device families provide advanced primitives for tackling reset. If these exist,
        # they should be used instead.
        if name == "sync" and self.default_clk is not None:
            clk_i = self.request(self.default_clk).i
            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            m = Module()
            m.domains += ClockDomain("sync")
            m.d.comb += ClockSignal("sync").eq(clk_i)
            m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
            return m

    def prepare(self, elaboratable, name="top", **kwargs):
        assert not self._prepared
        self._prepared = True

        fragment = Fragment.get(elaboratable, self)
        fragment = SampleLowerer()(fragment)
        fragment._propagate_domains(self.create_missing_domain, platform=self)
        fragment = DomainLowerer()(fragment)

        def add_pin_fragment(pin, pin_fragment):
            pin_fragment = Fragment.get(pin_fragment, self)
            if not isinstance(pin_fragment, Instance):
                pin_fragment.flatten = True
            fragment.add_subfragment(pin_fragment, name="pin_{}".format(pin.name))

        for pin, port, attrs, invert in self.iter_single_ended_pins():
            if pin.dir == "i":
                add_pin_fragment(pin, self.get_input(pin, port, attrs, invert))
            if pin.dir == "o":
                add_pin_fragment(pin, self.get_output(pin, port, attrs, invert))
            if pin.dir == "oe":
                add_pin_fragment(pin, self.get_tristate(pin, port, attrs, invert))
            if pin.dir == "io":
                add_pin_fragment(pin, self.get_input_output(pin, port, attrs, invert))

        for pin, port, attrs, invert in self.iter_differential_pins():
            if pin.dir == "i":
                add_pin_fragment(pin, self.get_diff_input(pin, port, attrs, invert))
            if pin.dir == "o":
                add_pin_fragment(pin, self.get_diff_output(pin, port, attrs, invert))
            if pin.dir == "oe":
                add_pin_fragment(pin, self.get_diff_tristate(pin, port, attrs, invert))
            if pin.dir == "io":
                add_pin_fragment(pin, self.get_diff_input_output(pin, port, attrs, invert))

        fragment._propagate_ports(ports=self.iter_ports(), all_undef_as_ports=False)
        return self.toolchain_prepare(fragment, name, **kwargs)

    @abstractmethod
    def toolchain_prepare(self, fragment, name, **kwargs):
        """
        Convert the ``fragment`` and constraints recorded in this :class:`Platform` into
        a :class:`BuildPlan`.
        """
        raise NotImplementedError # :nocov:

    def toolchain_program(self, products, name, **kwargs):
        """
        Extract bitstream for fragment ``name`` from ``products`` and download it to a target.
        """
        raise NotImplementedError("Platform '{}' does not support programming"
                                  .format(type(self).__name__))

    def _check_feature(self, feature, pin, attrs, valid_xdrs, valid_attrs):
        if len(valid_xdrs) == 0:
            raise NotImplementedError("Platform '{}' does not support {}"
                                      .format(type(self).__name__, feature))
        elif pin.xdr not in valid_xdrs:
            raise NotImplementedError("Platform '{}' does not support {} for XDR {}"
                                      .format(type(self).__name__, feature, pin.xdr))

        if not valid_attrs and attrs:
            raise NotImplementedError("Platform '{}' does not support attributes for {}"
                                      .format(type(self).__name__, feature))

    @staticmethod
    def _invert_if(invert, value):
        if invert:
            return ~value
        else:
            return value

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0,), valid_attrs=None)

        m = Module()
        m.d.comb += pin.i.eq(self._invert_if(invert, port))
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0,), valid_attrs=None)

        m = Module()
        m.d.comb += port.eq(self._invert_if(invert, pin.o))
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0,), valid_attrs=None)

        m = Module()
        m.submodules += Instance("$tribuf",
            p_WIDTH=pin.width,
            i_EN=pin.oe,
            i_A=self._invert_if(invert, pin.o),
            o_Y=port,
        )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0,), valid_attrs=None)

        m = Module()
        m.submodules += Instance("$tribuf",
            p_WIDTH=pin.width,
            i_EN=pin.oe,
            i_A=self._invert_if(invert, pin.o),
            o_Y=port,
        )
        m.d.comb += pin.i.eq(self._invert_if(invert, port))
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(), valid_attrs=None)

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(), valid_attrs=None)

    def get_diff_tristate(self, pin, port, attrs, invert):
        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=(), valid_attrs=None)

    def get_diff_input_output(self, pin, port, attrs, invert):
        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=(), valid_attrs=None)


class TemplatedPlatform(Platform):
    toolchain         = property(abstractmethod(lambda: None))
    file_templates    = property(abstractmethod(lambda: None))
    command_templates = property(abstractmethod(lambda: None))

    build_script_templates = {
        "build_{{name}}.sh": """
            # {{autogenerated}}
            set -e{{verbose("x")}}
            {% for var in platform._all_toolchain_env_vars %}
            [ -n "${{var}}" ] && . "${{var}}"
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "build_{{name}}.bat": """
            @rem {{autogenerated}}
            {{quiet("@echo off")}}
            {% for var in platform._all_toolchain_env_vars %}
            if defined {{var}} call %{{var}}%
            {% endfor %}
            {{emit_commands("bat")}}
        """,
    }

    def iter_clock_constraints(self):
        for net_signal, port_signal, frequency in super().iter_clock_constraints():
            # Skip any clock constraints placed on signals that are never used in the design.
            # Otherwise, it will cause a crash in the vendor platform if it supports clock
            # constraints on non-port nets.
            if net_signal not in self._name_map:
                continue
            yield net_signal, port_signal, frequency

    def toolchain_prepare(self, fragment, name, **kwargs):
        # Restrict the name of the design to a strict alphanumeric character set. Platforms will
        # interpolate the name of the design in many different contexts: filesystem paths, Python
        # scripts, Tcl scripts, ad-hoc constraint files, and so on. It is not practical to add
        # escaping code that handles every one of their edge cases, so make sure we never hit them
        # in the first place.
        invalid_char = re.match(r"[^A-Za-z0-9_]", name)
        if invalid_char:
            raise ValueError("Design name {!r} contains invalid character {!r}; only alphanumeric "
                             "characters are valid in design names"
                             .format(name, invalid_char.group(0)))

        # This notice serves a dual purpose: to explain that the file is autogenerated,
        # and to incorporate the Amaranth version into generated code.
        autogenerated = "Automatically generated by Amaranth {}. Do not edit.".format(__version__)

        rtlil_text, self._name_map = rtlil.convert_fragment(fragment, name=name)

        # Retrieve an override specified in either the environment or as a kwarg.
        # expected_type parameter is used to assert the type of kwargs, passing `None` will disable
        # type checking.
        def _extract_override(var, *, expected_type):
            var_env = "AMARANTH_{}".format(var)
            if var_env in os.environ:
                # On Windows, there is no way to define an "empty but set" variable; it is tempting
                # to use a quoted empty string, but it doesn't do what one would expect. Recognize
                # this as a useful pattern anyway, and treat `set VAR=""` on Windows the same way
                # `export VAR=` is treated on Linux.
                if var_env in os.environ:
                    var_env_value = os.environ[var_env]
                return re.sub(r'^\"\"$', "", var_env_value)
            elif var in kwargs:
                kwarg = kwargs[var]
                if issubclass(expected_type, str) and not isinstance(kwarg, str) and isinstance(kwarg, Iterable):
                    kwarg = " ".join(kwarg)
                if not isinstance(kwarg, expected_type) and not expected_type is None:
                    raise TypeError("Override '{}' must be a {}, not {!r}".format(var, expected_type.__name__, kwarg))
                return kwarg
            else:
                return jinja2.Undefined(name=var)

        def get_override(var):
            value = _extract_override(var, expected_type=str)
            return value

        def get_override_flag(var):
            value = _extract_override(var, expected_type=bool)
            if isinstance(value, str):
                value = value.lower()
                if value in ("0", "no", "n", "false", ""):
                    return False
                if value in ("1", "yes", "y", "true"):
                    return True
                else:
                    raise ValueError("Override '{}' must be one of "
                                     "(\"0\", \"n\", \"no\", \"false\", \"\") "
                                     "or "
                                     "(\"1\", \"y\", \"yes\", \"true\"), not {!r}"
                                     .format(var, value))
            return value

        def emit_rtlil():
            return rtlil_text

        def emit_verilog(opts=()):
            return verilog._convert_rtlil_text(rtlil_text,
                strip_internal_attrs=True, write_verilog_opts=opts)

        def emit_debug_verilog(opts=()):
            if not get_override_flag("debug_verilog"):
                return "/* Debug Verilog generation was disabled. */"
            else:
                return verilog._convert_rtlil_text(rtlil_text,
                    strip_internal_attrs=False, write_verilog_opts=opts)

        def emit_commands(syntax):
            commands = []

            for name in self.required_tools:
                env_var = tool_env_var(name)
                if syntax == "sh":
                    template = ": ${{{env_var}:={name}}}"
                elif syntax == "bat":
                    template = \
                        "if [%{env_var}%] equ [\"\"] set {env_var}=\n" \
                        "if [%{env_var}%] equ [] set {env_var}={name}"
                else:
                    assert False
                commands.append(template.format(env_var=env_var, name=name))

            for index, command_tpl in enumerate(self.command_templates):
                command = render(command_tpl, origin="<command#{}>".format(index + 1),
                                 syntax=syntax)
                command = re.sub(r"\s+", " ", command)
                if syntax == "sh":
                    commands.append(command)
                elif syntax == "bat":
                    commands.append(command + " || exit /b")
                else:
                    assert False

            return "\n".join(commands)

        @jinja2.pass_context
        def invoke_tool(context, name):
            env_var = tool_env_var(name)
            if context.parent["syntax"] == "sh":
                return "\"${}\"".format(env_var)
            elif context.parent["syntax"] == "bat":
                return "%{}%".format(env_var)
            else:
                assert False

        def options(opts):
            if isinstance(opts, str):
                return opts
            else:
                return " ".join(opts)

        def hierarchy(signal, separator):
            return separator.join(self._name_map[signal][1:])

        def ascii_escape(string):
            def escape_one(match):
                if match.group(1) is None:
                    return match.group(2)
                else:
                    return "_{:02x}_".format(ord(match.group(1)[0]))
            return "".join(escape_one(m) for m in re.finditer(r"([^A-Za-z0-9_])|(.)", string))

        def tcl_escape(string):
            return "{" + re.sub(r"([{}\\])", r"\\\1", string) + "}"

        def tcl_quote(string):
            return '"' + re.sub(r"([$[\\])", r"\\\1", string) + '"'

        def verbose(arg):
            if get_override_flag("verbose"):
                return arg
            else:
                return jinja2.Undefined(name="quiet")

        def quiet(arg):
            if get_override_flag("verbose"):
                return jinja2.Undefined(name="quiet")
            else:
                return arg

        def render(source, origin, syntax=None):
            try:
                source   = textwrap.dedent(source).strip()
                compiled = jinja2.Template(source,
                    trim_blocks=True, lstrip_blocks=True, undefined=jinja2.StrictUndefined)
                compiled.environment.filters["options"] = options
                compiled.environment.filters["hierarchy"] = hierarchy
                compiled.environment.filters["ascii_escape"] = ascii_escape
                compiled.environment.filters["tcl_escape"] = tcl_escape
                compiled.environment.filters["tcl_quote"] = tcl_quote
            except jinja2.TemplateSyntaxError as e:
                e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
                raise
            return compiled.render({
                "name": name,
                "platform": self,
                "emit_rtlil": emit_rtlil,
                "emit_verilog": emit_verilog,
                "emit_debug_verilog": emit_debug_verilog,
                "emit_commands": emit_commands,
                "syntax": syntax,
                "invoke_tool": invoke_tool,
                "get_override": get_override,
                "get_override_flag": get_override_flag,
                "verbose": verbose,
                "quiet": quiet,
                "autogenerated": autogenerated,
            })

        plan = BuildPlan(script="build_{}".format(name))
        for filename_tpl, content_tpl in self.file_templates.items():
            plan.add_file(render(filename_tpl, origin=filename_tpl),
                          render(content_tpl, origin=content_tpl))
        for filename, content in self.extra_files.items():
            plan.add_file(filename, content)
        return plan


# amaranth/build/res.py

from collections import OrderedDict

from ..hdl.ast import *
from ..hdl.rec import *
from ..lib.io import *

from .dsl import *


__all__ = ["ResourceError", "ResourceManager"]


class ResourceError(Exception):
    pass


class ResourceManager:
    def __init__(self, resources, connectors):
        self.resources  = OrderedDict()
        self._requested = OrderedDict()
        self._phys_reqd = OrderedDict()

        self.connectors = OrderedDict()
        self._conn_pins = OrderedDict()

        # Constraint lists
        self._ports     = []
        self._clocks    = SignalDict()

        self.add_resources(resources)
        self.add_connectors(connectors)

    def add_resources(self, resources):
        for res in resources:
            if not isinstance(res, Resource):
                raise TypeError("Object {!r} is not a Resource".format(res))
            if (res.name, res.number) in self.resources:
                raise NameError("Trying to add {!r}, but {!r} has the same name and number"
                                .format(res, self.resources[res.name, res.number]))
            self.resources[res.name, res.number] = res

    def add_connectors(self, connectors):
        for conn in connectors:
            if not isinstance(conn, Connector):
                raise TypeError("Object {!r} is not a Connector".format(conn))
            if (conn.name, conn.number) in self.connectors:
                raise NameError("Trying to add {!r}, but {!r} has the same name and number"
                                .format(conn, self.connectors[conn.name, conn.number]))
            self.connectors[conn.name, conn.number] = conn

            for conn_pin, plat_pin in conn:
                assert conn_pin not in self._conn_pins
                self._conn_pins[conn_pin] = plat_pin

    def lookup(self, name, number=0):
        if (name, number) not in self.resources:
            raise ResourceError("Resource {}#{} does not exist"
                                  .format(name, number))
        return self.resources[name, number]

    def request(self, name, number=0, *, dir=None, xdr=None):
        resource = self.lookup(name, number)
        if (resource.name, resource.number) in self._requested:
            raise ResourceError("Resource {}#{} has already been requested"
                                .format(name, number))

        def merge_options(subsignal, dir, xdr):
            if isinstance(subsignal.ios[0], Subsignal):
                if dir is None:
                    dir = dict()
                if xdr is None:
                    xdr = dict()
                if not isinstance(dir, dict):
                    raise TypeError("Directions must be a dict, not {!r}, because {!r} "
                                    "has subsignals"
                                    .format(dir, subsignal))
                if not isinstance(xdr, dict):
                    raise TypeError("Data rate must be a dict, not {!r}, because {!r} "
                                    "has subsignals"
                                    .format(xdr, subsignal))
                for sub in subsignal.ios:
                    sub_dir = dir.get(sub.name, None)
                    sub_xdr = xdr.get(sub.name, None)
                    dir[sub.name], xdr[sub.name] = merge_options(sub, sub_dir, sub_xdr)
            else:
                if dir is None:
                    dir = subsignal.ios[0].dir
                if xdr is None:
                    xdr = 0
                if dir not in ("i", "o", "oe", "io", "-"):
                    raise TypeError("Direction must be one of \"i\", \"o\", \"oe\", \"io\", "
                                    "or \"-\", not {!r}"
                                    .format(dir))
                if dir != subsignal.ios[0].dir and \
                        not (subsignal.ios[0].dir == "io" or dir == "-"):
                    raise ValueError("Direction of {!r} cannot be changed from \"{}\" to \"{}\"; "
                                     "direction can be changed from \"io\" to \"i\", \"o\", or "
                                     "\"oe\", or from anything to \"-\""
                                     .format(subsignal.ios[0], subsignal.ios[0].dir, dir))
                if not isinstance(xdr, int) or xdr < 0:
                    raise ValueError("Data rate of {!r} must be a non-negative integer, not {!r}"
                                     .format(subsignal.ios[0], xdr))
            return dir, xdr

        def resolve(resource, dir, xdr, name, attrs):
            for attr_key, attr_value in attrs.items():
                if hasattr(attr_value, "__call__"):
                    attr_value = attr_value(self)
                    assert attr_value is None or isinstance(attr_value, str)
                if attr_value is None:
                    del attrs[attr_key]
                else:
                    attrs[attr_key] = attr_value

            if isinstance(resource.ios[0], Subsignal):
                fields = OrderedDict()
                for sub in resource.ios:
                    fields[sub.name] = resolve(sub, dir[sub.name], xdr[sub.name],
                                               name="{}__{}".format(name, sub.name),
                                               attrs={**attrs, **sub.attrs})
                return Record([
                    (f_name, f.layout) for (f_name, f) in fields.items()
                ], fields=fields, name=name)

            elif isinstance(resource.ios[0], (Pins, DiffPairs)):
                phys = resource.ios[0]
                if isinstance(phys, Pins):
                    phys_names = phys.names
                    port = Record([("io", len(phys))], name=name)
                if isinstance(phys, DiffPairs):
                    phys_names = []
                    record_fields = []
                    if not self.should_skip_port_component(None, attrs, "p"):
                        phys_names += phys.p.names
                        record_fields.append(("p", len(phys)))
                    if not self.should_skip_port_component(None, attrs, "n"):
                        phys_names += phys.n.names
                        record_fields.append(("n", len(phys)))
                    port = Record(record_fields, name=name)
                if dir == "-":
                    pin = None
                else:
                    pin = Pin(len(phys), dir, xdr=xdr, name=name)

                for phys_name in phys_names:
                    if phys_name in self._phys_reqd:
                        raise ResourceError("Resource component {} uses physical pin {}, but it "
                                            "is already used by resource component {} that was "
                                            "requested earlier"
                                            .format(name, phys_name, self._phys_reqd[phys_name]))
                    self._phys_reqd[phys_name] = name

                self._ports.append((resource, pin, port, attrs))

                if pin is not None and resource.clock is not None:
                    self.add_clock_constraint(pin.i, resource.clock.frequency)

                return pin if pin is not None else port

            else:
                assert False # :nocov:

        value = resolve(resource,
            *merge_options(resource, dir, xdr),
            name="{}_{}".format(resource.name, resource.number),
            attrs=resource.attrs)
        self._requested[resource.name, resource.number] = value
        return value

    def iter_single_ended_pins(self):
        for res, pin, port, attrs in self._ports:
            if pin is None:
                continue
            if isinstance(res.ios[0], Pins):
                yield pin, port, attrs, res.ios[0].invert

    def iter_differential_pins(self):
        for res, pin, port, attrs in self._ports:
            if pin is None:
                continue
            if isinstance(res.ios[0], DiffPairs):
                yield pin, port, attrs, res.ios[0].invert

    def should_skip_port_component(self, port, attrs, component):
        return False

    def iter_ports(self):
        for res, pin, port, attrs in self._ports:
            if isinstance(res.ios[0], Pins):
                if not self.should_skip_port_component(port, attrs, "io"):
                    yield port.io
            elif isinstance(res.ios[0], DiffPairs):
                if not self.should_skip_port_component(port, attrs, "p"):
                    yield port.p
                if not self.should_skip_port_component(port, attrs, "n"):
                    yield port.n
            else:
                assert False

    def iter_port_constraints(self):
        for res, pin, port, attrs in self._ports:
            if isinstance(res.ios[0], Pins):
                if not self.should_skip_port_component(port, attrs, "io"):
                    yield port.io.name, res.ios[0].map_names(self._conn_pins, res), attrs
            elif isinstance(res.ios[0], DiffPairs):
                if not self.should_skip_port_component(port, attrs, "p"):
                    yield port.p.name, res.ios[0].p.map_names(self._conn_pins, res), attrs
                if not self.should_skip_port_component(port, attrs, "n"):
                    yield port.n.name, res.ios[0].n.map_names(self._conn_pins, res), attrs
            else:
                assert False

    def iter_port_constraints_bits(self):
        for port_name, pin_names, attrs in self.iter_port_constraints():
            if len(pin_names) == 1:
                yield port_name, pin_names[0], attrs
            else:
                for bit, pin_name in enumerate(pin_names):
                    yield "{}[{}]".format(port_name, bit), pin_name, attrs

    def add_clock_constraint(self, clock, frequency):
        if not isinstance(clock, Signal):
            raise TypeError("Object {!r} is not a Signal".format(clock))
        if not isinstance(frequency, (int, float)):
            raise TypeError("Frequency must be a number, not {!r}".format(frequency))

        if clock in self._clocks:
            raise ValueError("Cannot add clock constraint on {!r}, which is already constrained "
                             "to {} Hz"
                             .format(clock, self._clocks[clock]))
        else:
            self._clocks[clock] = float(frequency)

    def iter_clock_constraints(self):
        # Back-propagate constraints through the input buffer. For clock constraints on pins
        # (the majority of cases), toolchains work better if the constraint is defined on the pin
        # and not on the buffered internal net; and if the toolchain is advanced enough that
        # it considers clock phase and delay of the input buffer, it is *necessary* to define
        # the constraint on the pin to match the designer's expectation of phase being referenced
        # to the pin.
        #
        # Constraints on nets with no corresponding input pin (e.g. PLL or SERDES outputs) are not
        # affected.
        pin_i_to_port = SignalDict()
        for res, pin, port, attrs in self._ports:
            if hasattr(pin, "i"):
                if isinstance(res.ios[0], Pins):
                    pin_i_to_port[pin.i] = port.io
                elif isinstance(res.ios[0], DiffPairs):
                    pin_i_to_port[pin.i] = port.p
                else:
                    assert False

        for net_signal, frequency in self._clocks.items():
            port_signal = pin_i_to_port.get(net_signal)
            yield net_signal, port_signal, frequency


# amaranth/build/run.py

from collections import OrderedDict
from contextlib import contextmanager
from abc import ABCMeta, abstractmethod
import os
import sys
import subprocess
import tempfile
import zipfile
import hashlib
import pathlib


__all__ = ["BuildPlan", "BuildProducts", "LocalBuildProducts", "RemoteSSHBuildProducts"]



class BuildPlan:
    def __init__(self, script):
        """A build plan.

        Parameters
        ----------
        script : str
            The base name (without extension) of the script that will be executed.
        """
        self.script = script
        self.files  = OrderedDict()

    def add_file(self, filename, content):
        """
        Add ``content``, which can be a :class:`str`` or :class:`bytes`, to the build plan
        as ``filename``. The file name can be a relative path with directories separated by
        forward slashes (``/``).
        """
        assert isinstance(filename, str) and filename not in self.files
        if (pathlib.PurePosixPath(filename).is_absolute() or
                pathlib.PureWindowsPath(filename).is_absolute()):
            raise ValueError(f"Filename {filename!r} must not be an absolute path")
        self.files[filename] = content

    def digest(self, size=64):
        """
        Compute a `digest`, a short byte sequence deterministically and uniquely identifying
        this build plan.
        """
        hasher = hashlib.blake2b(digest_size=size)
        for filename in sorted(self.files):
            hasher.update(filename.encode("utf-8"))
            content = self.files[filename]
            if isinstance(content, str):
                content = content.encode("utf-8")
            hasher.update(content)
        hasher.update(self.script.encode("utf-8"))
        return hasher.digest()

    def archive(self, file):
        """
        Archive files from the build plan into ``file``, which can be either a filename, or
        a file-like object. The produced archive is deterministic: exact same files will
        always produce exact same archive.
        """
        with zipfile.ZipFile(file, "w") as archive:
            # Write archive members in deterministic order and with deterministic timestamp.
            for filename in sorted(self.files):
                archive.writestr(zipfile.ZipInfo(filename), self.files[filename])

    def execute_local(self, root="build", *, run_script=True, env=None):
        """
        Execute build plan using the local strategy. Files from the build plan are placed in
        the build root directory ``root``, and, if ``run_script`` is ``True``, the script
        appropriate for the platform (``{script}.bat`` on Windows, ``{script}.sh`` elsewhere)
        is executed in the build root. If ``env`` is not ``None``, the environment is replaced
        with ``env``.

        Returns :class:`LocalBuildProducts`.
        """
        os.makedirs(root, exist_ok=True)
        cwd = os.getcwd()
        try:
            os.chdir(root)

            for filename, content in self.files.items():
                filename = pathlib.Path(filename)
                # Forbid parent directory components and absolute paths completely to avoid
                # the possibility of writing outside the build root.
                assert not filename.is_absolute() and ".." not in filename.parts
                dirname = os.path.dirname(filename)
                if dirname:
                    os.makedirs(dirname, exist_ok=True)

                if isinstance(content, str):
                    content = content.encode("utf-8")
                with open(filename, "wb") as f:
                    f.write(content)

            if run_script:
                if sys.platform.startswith("win32"):
                    # Without "call", "cmd /c {}.bat" will return 0.
                    # See https://stackoverflow.com/a/30736987 for a detailed explanation of why.
                    # Running the script manually from a command prompt is unaffected.
                    subprocess.check_call(["cmd", "/c", "call {}.bat".format(self.script)],
                                          env=os.environ if env is None else env)
                else:
                    subprocess.check_call(["sh", "{}.sh".format(self.script)],
                                          env=os.environ if env is None else env)

            return LocalBuildProducts(os.getcwd())

        finally:
            os.chdir(cwd)

    def execute_remote_ssh(self, *, connect_to={}, root, run_script=True):
        """
        Execute build plan using the remote SSH strategy. Files from the build
        plan are transferred via SFTP to the directory ``root`` on a  remote
        server. If ``run_script`` is ``True``, the ``paramiko`` SSH client will
        then run ``{script}.sh``. ``root`` can either be an absolute or
        relative (to the login directory) path.

        ``connect_to`` is a dictionary that holds all input arguments to
        ``paramiko``'s ``SSHClient.connect``
        (`documentation <http://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.connect>`_).
        At a minimum, the ``hostname`` input argument must be supplied in this
        dictionary as the remote server.

        Returns :class:`RemoteSSHBuildProducts`.
        """
        from paramiko import SSHClient

        with SSHClient() as client:
            client.load_system_host_keys()
            client.connect(**connect_to)

            with client.open_sftp() as sftp:
                def mkdir_exist_ok(path):
                    try:
                        sftp.mkdir(str(path))
                    except IOError as e:
                        # mkdir fails if directory exists. This is fine in amaranth.build.
                        # Reraise errors containing e.errno info.
                        if e.errno:
                            raise e

                def mkdirs(path):
                    # Iteratively create parent directories of a file by iterating over all
                    # parents except for the root ("."). Slicing the parents results in
                    # TypeError, so skip over the root ("."); this also handles files
                    # already in the root directory.
                    for parent in reversed(path.parents):
                        if parent == pathlib.PurePosixPath("."):
                            continue
                        else:
                            mkdir_exist_ok(parent)

                mkdir_exist_ok(root)

                sftp.chdir(root)
                for filename, content in self.files.items():
                    filename = pathlib.PurePosixPath(filename)
                    assert ".." not in filename.parts

                    mkdirs(filename)

                    mode = "t" if isinstance(content, str) else "b"
                    with sftp.file(str(filename), "w" + mode) as f:
                        f.set_pipelined()
                        # "b/t" modifier ignored in SFTP.
                        if mode == "t":
                            f.write(content.encode("utf-8"))
                        else:
                            f.write(content)

            if run_script:
                transport = client.get_transport()
                channel = transport.open_session()
                channel.set_combine_stderr(True)

                cmd = (f"if [ -f ~/.profile ]; then . ~/.profile; fi && "
                       f"cd {root} && exec $0 {self.script}.sh")
                channel.exec_command(f"sh -c '{cmd}'")

                # Show the output from the server while products are built.
                buf = channel.recv(1024)
                while buf:
                    print(buf.decode("utf-8", errors="replace"), end="")
                    buf = channel.recv(1024)

        return RemoteSSHBuildProducts(connect_to, root)

    def execute(self):
        """
        Execute build plan using the default strategy. Use one of the ``execute_*`` methods
        explicitly to have more control over the strategy.
        """
        return self.execute_local()


class BuildProducts(metaclass=ABCMeta):
    @abstractmethod
    def get(self, filename, mode="b"):
        """
        Extract ``filename`` from build products, and return it as a :class:`bytes` (if ``mode``
        is ``"b"``) or a :class:`str` (if ``mode`` is ``"t"``).
        """
        assert mode in ("b", "t")

    @contextmanager
    def extract(self, *filenames):
        """
        Extract ``filenames`` from build products, place them in an OS-specific temporary file
        location, with the extension preserved, and delete them afterwards. This method is used
        as a context manager, e.g.: ::

            with products.extract("bitstream.bin", "programmer.cfg") \
                    as bitstream_filename, config_filename:
                subprocess.check_call(["program", "-c", config_filename, bitstream_filename])
        """
        files = []
        try:
            for filename in filenames:
                # On Windows, a named temporary file (as created by Python) is not accessible to
                # others if it's still open within the Python process, so we close it and delete
                # it manually.
                file = tempfile.NamedTemporaryFile(
                    prefix="amaranth_", suffix="_" + os.path.basename(filename),
                    delete=False)
                files.append(file)
                file.write(self.get(filename))
                file.close()

            if len(files) == 0:
                return (yield)
            elif len(files) == 1:
                return (yield files[0].name)
            else:
                return (yield [file.name for file in files])
        finally:
            for file in files:
                os.unlink(file.name)


class LocalBuildProducts(BuildProducts):
    def __init__(self, root):
        # We provide no guarantees that files will be available on the local filesystem (i.e. in
        # any way other than through `products.get()`) in general, so downstream code must never
        # rely on this, even when we happen to use a local build most of the time.
        self.__root = root

    def get(self, filename, mode="b"):
        super().get(filename, mode)
        with open(os.path.join(self.__root, filename), "r" + mode) as f:
            return f.read()


class RemoteSSHBuildProducts(BuildProducts):
    def __init__(self, connect_to, root):
        self.__connect_to = connect_to
        self.__root = root

    def get(self, filename, mode="b"):
        super().get(filename, mode)

        from paramiko import SSHClient

        with SSHClient() as client:
            client.load_system_host_keys()
            client.connect(**self.__connect_to)

            with client.open_sftp() as sftp:
                sftp.chdir(self.__root)

                with sftp.file(filename, "r" + mode) as f:
                    f.prefetch()
                    # "b/t" modifier ignored in SFTP.
                    if mode == "t":
                        return f.read().decode("utf-8")
                    else:
                        return f.read()


# amaranth/compat/__init__.py

from .fhdl.structure import *
from .fhdl.module import *
from .fhdl.specials import *
from .fhdl.bitcontainer import *
from .fhdl.decorators import *
# from .fhdl.simplify import *

from .sim import *

from .genlib.record import *
from .genlib.fsm import *


# amaranth/hdl/__init__.py

from .ast import Shape, unsigned, signed
from .ast import Value, Const, C, Mux, Cat, Repl, Array, Signal, ClockSignal, ResetSignal
from .dsl import Module
from .cd import ClockDomain
from .ir import Elaboratable, Fragment, Instance
from .mem import Memory
from .rec import Record
from .xfrm import DomainRenamer, ResetInserter, EnableInserter


__all__ = [
    "Shape", "unsigned", "signed",
    "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
    "Module",
    "ClockDomain",
    "Elaboratable", "Fragment", "Instance",
    "Memory",
    "Record",
    "DomainRenamer", "ResetInserter", "EnableInserter",
]


# amaranth/hdl/ast.py

from abc import ABCMeta, abstractmethod
import inspect
import warnings
import functools
from collections import OrderedDict
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
from enum import Enum
from itertools import chain

from .. import tracer
from .._utils import *
from .._utils import _ignore_deprecated
from .._unused import *


__all__ = [
    "Shape", "signed", "unsigned", "ShapeCastable",
    "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
    "Array", "ArrayProxy",
    "Signal", "ClockSignal", "ResetSignal",
    "ValueCastable",
    "Sample", "Past", "Stable", "Rose", "Fell", "Initial",
    "Statement", "Switch",
    "Property", "Assign", "Assert", "Assume", "Cover",
    "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", "SignalSet",
]


class DUID:
    """Deterministic Unique IDentifier."""
    __next_uid = 0
    def __init__(self):
        self.duid = DUID.__next_uid
        DUID.__next_uid += 1


class ShapeCastable:
    """Interface of user-defined objects that can be cast to :class:`Shape` s.

    An object deriving from :class:`ShapeCastable` is automatically converted to a :class:`Shape`
    when it is used in a context where a :class:`Shape` is expected. Such objects can contain
    a richer description of the shape than what is supported by the core Amaranth language, yet
    still be transparently used with it.
    """
    def __init_subclass__(cls, **kwargs):
        if not hasattr(cls, "as_shape"):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
                            f"the `as_shape` method")
        if not (hasattr(cls, "__call__") and inspect.isfunction(cls.__call__)):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
                            f"the `__call__` method")
        if not hasattr(cls, "const"):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
                            f"the `const` method")


class Shape:
    """Bit width and signedness of a value.

    A ``Shape`` can be constructed using:
      * explicit bit width and signedness;
      * aliases :func:`signed` and :func:`unsigned`;
      * casting from a variety of objects.

    A ``Shape`` can be cast from:
      * an integer, where the integer specifies the bit width;
      * a range, where the result is wide enough to represent any element of the range, and is
        signed if any element of the range is signed;
      * an :class:`Enum` with all integer members or :class:`IntEnum`, where the result is wide
        enough to represent any member of the enumeration, and is signed if any member of
        the enumeration is signed.

    Parameters
    ----------
    width : int
        The number of bits in the representation, including the sign bit (if any).
    signed : bool
        If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
    """
    def __init__(self, width=1, signed=False):
        if not isinstance(width, int) or width < 0:
            raise TypeError("Width must be a non-negative integer, not {!r}"
                            .format(width))
        self.width = width
        self.signed = signed

    # The algorithm for inferring shape for standard Python enumerations is factored out so that
    # `Shape.cast()` and Amaranth's `EnumMeta.as_shape()` can both use it.
    @staticmethod
    def _cast_plain_enum(obj):
        signed = False
        width  = 0
        for member in obj:
            try:
                member_shape = Const.cast(member.value).shape()
            except TypeError as e:
                raise TypeError("Only enumerations whose members have constant-castable "
                                "values can be used in Amaranth code")
            if not signed and member_shape.signed:
                signed = True
                width  = max(width + 1, member_shape.width)
            elif signed and not member_shape.signed:
                width  = max(width, member_shape.width + 1)
            else:
                width  = max(width, member_shape.width)
        return Shape(width, signed)

    @staticmethod
    def cast(obj, *, src_loc_at=0):
        while True:
            if isinstance(obj, Shape):
                return obj
            elif isinstance(obj, ShapeCastable):
                new_obj = obj.as_shape()
            elif isinstance(obj, int):
                return Shape(obj)
            elif isinstance(obj, range):
                if len(obj) == 0:
                    return Shape(0, obj.start < 0)
                signed = obj[0] < 0 or obj[-1] < 0
                width  = max(bits_for(obj[0], signed),
                             bits_for(obj[-1], signed))
                return Shape(width, signed)
            elif isinstance(obj, type) and issubclass(obj, Enum):
                # For compatibility with third party enumerations, handle them as if they were
                # defined as subclasses of lib.enum.Enum with no explicitly specified shape.
                return Shape._cast_plain_enum(obj)
            else:
                raise TypeError("Object {!r} cannot be converted to an Amaranth shape".format(obj))
            if new_obj is obj:
                raise RecursionError("Shape-castable object {!r} casts to itself".format(obj))
            obj = new_obj

    def __repr__(self):
        if self.signed:
            return "signed({})".format(self.width)
        else:
            return "unsigned({})".format(self.width)

    def __eq__(self, other):
        return (isinstance(other, Shape) and
                self.width == other.width and self.signed == other.signed)


def unsigned(width):
    """Shorthand for ``Shape(width, signed=False)``."""
    return Shape(width, signed=False)


def signed(width):
    """Shorthand for ``Shape(width, signed=True)``."""
    return Shape(width, signed=True)


class Value(metaclass=ABCMeta):
    @staticmethod
    def cast(obj):
        """Converts ``obj`` to an Amaranth value.

        Booleans and integers are wrapped into a :class:`Const`. Enumerations whose members are
        all integers are converted to a :class:`Const` with a shape that fits every member.
        :class:`ValueCastable` objects are recursively cast to an Amaranth value.
        """
        while True:
            if isinstance(obj, Value):
                return obj
            elif isinstance(obj, ValueCastable):
                new_obj = obj.as_value()
            elif isinstance(obj, Enum):
                return Const(obj.value, Shape.cast(type(obj)))
            elif isinstance(obj, int):
                return Const(obj)
            else:
                raise TypeError("Object {!r} cannot be converted to an Amaranth value".format(obj))
            if new_obj is obj:
                raise RecursionError("Value-castable object {!r} casts to itself".format(obj))
            obj = new_obj

    def __init__(self, *, src_loc_at=0):
        super().__init__()
        self.src_loc = tracer.get_src_loc(1 + src_loc_at)

    def __bool__(self):
        raise TypeError("Attempted to convert Amaranth value to Python boolean")

    def __pos__(self):
        return self

    def __invert__(self):
        return Operator("~", [self])
    def __neg__(self):
        return Operator("-", [self])

    def __add__(self, other):
        return Operator("+", [self, other])
    def __radd__(self, other):
        return Operator("+", [other, self])
    def __sub__(self, other):
        return Operator("-", [self, other])
    def __rsub__(self, other):
        return Operator("-", [other, self])

    def __mul__(self, other):
        return Operator("*", [self, other])
    def __rmul__(self, other):
        return Operator("*", [other, self])

    def __mod__(self, other):
        return Operator("%", [self, other])
    def __rmod__(self, other):
        return Operator("%", [other, self])
    def __floordiv__(self, other):
        return Operator("//", [self, other])
    def __rfloordiv__(self, other):
        return Operator("//", [other, self])

    def __check_shamt(self):
        if self.shape().signed:
            # Neither Python nor HDLs implement shifts by negative values; prohibit any shifts
            # by a signed value to make sure the shift amount can always be interpreted as
            # an unsigned value.
            raise TypeError("Shift amount must be unsigned")
    def __lshift__(self, other):
        other = Value.cast(other)
        other.__check_shamt()
        return Operator("<<", [self, other])
    def __rlshift__(self, other):
        self.__check_shamt()
        return Operator("<<", [other, self])
    def __rshift__(self, other):
        other = Value.cast(other)
        other.__check_shamt()
        return Operator(">>", [self, other])
    def __rrshift__(self, other):
        self.__check_shamt()
        return Operator(">>", [other, self])

    def __and__(self, other):
        return Operator("&", [self, other])
    def __rand__(self, other):
        return Operator("&", [other, self])
    def __xor__(self, other):
        return Operator("^", [self, other])
    def __rxor__(self, other):
        return Operator("^", [other, self])
    def __or__(self, other):
        return Operator("|", [self, other])
    def __ror__(self, other):
        return Operator("|", [other, self])

    def __eq__(self, other):
        return Operator("==", [self, other])
    def __ne__(self, other):
        return Operator("!=", [self, other])
    def __lt__(self, other):
        return Operator("<", [self, other])
    def __le__(self, other):
        return Operator("<=", [self, other])
    def __gt__(self, other):
        return Operator(">", [self, other])
    def __ge__(self, other):
        return Operator(">=", [self, other])

    def __abs__(self):
        if self.shape().signed:
            return Mux(self >= 0, self, -self)[:len(self)]
        else:
            return self

    def __len__(self):
        return self.shape().width

    def __getitem__(self, key):
        n = len(self)
        if isinstance(key, int):
            if key not in range(-n, n):
                raise IndexError(f"Index {key} is out of bounds for a {n}-bit value")
            if key < 0:
                key += n
            return Slice(self, key, key + 1, src_loc_at=1)
        elif isinstance(key, slice):
            start, stop, step = key.indices(n)
            if step != 1:
                return Cat(self[i] for i in range(start, stop, step))
            return Slice(self, start, stop, src_loc_at=1)
        else:
            raise TypeError("Cannot index value with {}".format(repr(key)))

    def as_unsigned(self):
        """Conversion to unsigned.

        Returns
        -------
        Value, out
            This ``Value`` reinterpreted as a unsigned integer.
        """
        return Operator("u", [self])

    def as_signed(self):
        """Conversion to signed.

        Returns
        -------
        Value, out
            This ``Value`` reinterpreted as a signed integer.
        """
        return Operator("s", [self])

    def bool(self):
        """Conversion to boolean.

        Returns
        -------
        Value, out
            ``1`` if any bits are set, ``0`` otherwise.
        """
        return Operator("b", [self])

    def any(self):
        """Check if any bits are ``1``.

        Returns
        -------
        Value, out
            ``1`` if any bits are set, ``0`` otherwise.
        """
        return Operator("r|", [self])

    def all(self):
        """Check if all bits are ``1``.

        Returns
        -------
        Value, out
            ``1`` if all bits are set, ``0`` otherwise.
        """
        return Operator("r&", [self])

    def xor(self):
        """Compute pairwise exclusive-or of every bit.

        Returns
        -------
        Value, out
            ``1`` if an odd number of bits are set, ``0`` if an even number of bits are set.
        """
        return Operator("r^", [self])

    def implies(premise, conclusion):
        """Implication.

        Returns
        -------
        Value, out
            ``0`` if ``premise`` is true and ``conclusion`` is not, ``1`` otherwise.
        """
        return ~premise | conclusion

    def bit_select(self, offset, width):
        """Part-select with bit granularity.

        Selects a constant width but variable offset part of a ``Value``, such that successive
        parts overlap by all but 1 bit.

        Parameters
        ----------
        offset : Value, int
            Index of first selected bit.
        width : int
            Number of selected bits.

        Returns
        -------
        Part, out
            Selected part of the ``Value``
        """
        offset = Value.cast(offset)
        if type(offset) is Const and isinstance(width, int):
            return self[offset.value:offset.value + width]
        return Part(self, offset, width, stride=1, src_loc_at=1)

    def word_select(self, offset, width):
        """Part-select with word granularity.

        Selects a constant width but variable offset part of a ``Value``, such that successive
        parts do not overlap.

        Parameters
        ----------
        offset : Value, int
            Index of first selected word.
        width : int
            Number of selected bits.

        Returns
        -------
        Part, out
            Selected part of the ``Value``
        """
        offset = Value.cast(offset)
        if type(offset) is Const and isinstance(width, int):
            return self[offset.value * width:(offset.value + 1) * width]
        return Part(self, offset, width, stride=width, src_loc_at=1)

    def matches(self, *patterns):
        """Pattern matching.

        Matches against a set of patterns, which may be integers or bit strings, recognizing
        the same grammar as ``Case()``.

        Parameters
        ----------
        patterns : int or str
            Patterns to match against.

        Returns
        -------
        Value, out
            ``1`` if any pattern matches the value, ``0`` otherwise.
        """
        matches = []
        # This code should accept exactly the same patterns as `with m.Case(...):`.
        for pattern in patterns:
            if isinstance(pattern, str) and any(bit not in "01- \t" for bit in pattern):
                raise SyntaxError("Match pattern '{}' must consist of 0, 1, and - (don't care) "
                                  "bits, and may include whitespace"
                                  .format(pattern))
            if (isinstance(pattern, str) and
                    len("".join(pattern.split())) != len(self)):
                raise SyntaxError("Match pattern '{}' must have the same width as match value "
                                  "(which is {})"
                                  .format(pattern, len(self)))
            if isinstance(pattern, str):
                pattern = "".join(pattern.split()) # remove whitespace
                mask    = int(pattern.replace("0", "1").replace("-", "0"), 2)
                pattern = int(pattern.replace("-", "0"), 2)
                matches.append((self & mask) == pattern)
            else:
                try:
                    orig_pattern, pattern = pattern, Const.cast(pattern)
                except TypeError as e:
                    raise SyntaxError("Match pattern must be a string or a constant-castable "
                                      "expression, not {!r}"
                                      .format(pattern)) from e
                pattern_len = bits_for(pattern.value)
                if pattern_len > len(self):
                    warnings.warn("Match pattern '{!r}' ({}'{:b}) is wider than match value "
                                  "(which has width {}); comparison will never be true"
                                  .format(orig_pattern, pattern_len, pattern.value, len(self)),
                                  SyntaxWarning, stacklevel=2)
                    continue
                matches.append(self == pattern)
        if not matches:
            return Const(1)
        elif len(matches) == 1:
            return matches[0]
        else:
            return Cat(*matches).any()

    def shift_left(self, amount):
        """Shift left by constant amount.

        Parameters
        ----------
        amount : int
            Amount to shift by.

        Returns
        -------
        Value, out
            If the amount is positive, the input shifted left. Otherwise, the input shifted right.
        """
        if not isinstance(amount, int):
            raise TypeError("Shift amount must be an integer, not {!r}".format(amount))
        if amount < 0:
            return self.shift_right(-amount)
        if self.shape().signed:
            return Cat(Const(0, amount), self).as_signed()
        else:
            return Cat(Const(0, amount), self) # unsigned

    def shift_right(self, amount):
        """Shift right by constant amount.

        Parameters
        ----------
        amount : int
            Amount to shift by.

        Returns
        -------
        Value, out
            If the amount is positive, the input shifted right. Otherwise, the input shifted left.
        """
        if not isinstance(amount, int):
            raise TypeError("Shift amount must be an integer, not {!r}".format(amount))
        if amount < 0:
            return self.shift_left(-amount)
        if self.shape().signed:
            return self[amount:].as_signed()
        else:
            return self[amount:] # unsigned

    def rotate_left(self, amount):
        """Rotate left by constant amount.

        Parameters
        ----------
        amount : int
            Amount to rotate by.

        Returns
        -------
        Value, out
            If the amount is positive, the input rotated left. Otherwise, the input rotated right.
        """
        if not isinstance(amount, int):
            raise TypeError("Rotate amount must be an integer, not {!r}".format(amount))
        if len(self) != 0:
            amount %= len(self)
        return Cat(self[-amount:], self[:-amount]) # meow :3

    def rotate_right(self, amount):
        """Rotate right by constant amount.

        Parameters
        ----------
        amount : int
            Amount to rotate by.

        Returns
        -------
        Value, out
            If the amount is positive, the input rotated right. Otherwise, the input rotated right.
        """
        if not isinstance(amount, int):
            raise TypeError("Rotate amount must be an integer, not {!r}".format(amount))
        if len(self) != 0:
            amount %= len(self)
        return Cat(self[amount:], self[:amount])

    def replicate(self, count):
        """Replication.

        A ``Value`` is replicated (repeated) several times to be used
        on the RHS of assignments::

            len(v.replicate(n)) == len(v) * n

        Parameters
        ----------
        count : int
            Number of replications.

        Returns
        -------
        Value, out
            Replicated value.
        """
        if not isinstance(count, int) or count < 0:
            raise TypeError("Replication count must be a non-negative integer, not {!r}"
                            .format(count))
        return Cat(self for _ in range(count))

    def eq(self, value):
        """Assignment.

        Parameters
        ----------
        value : Value, in
            Value to be assigned.

        Returns
        -------
        Assign
            Assignment statement that can be used in combinatorial or synchronous context.
        """
        return Assign(self, value, src_loc_at=1)

    @abstractmethod
    def shape(self):
        """Bit width and signedness of a value.

        Returns
        -------
        Shape
            See :class:`Shape`.

        Examples
        --------
        >>> Signal(8).shape()
        Shape(width=8, signed=False)
        >>> Const(0xaa).shape()
        Shape(width=8, signed=False)
        """
        pass # :nocov:

    def _lhs_signals(self):
        raise TypeError("Value {!r} cannot be used in assignments".format(self))

    @abstractmethod
    def _rhs_signals(self):
        pass # :nocov:


@final
class Const(Value):
    """A constant, literal integer value.

    Parameters
    ----------
    value : int
    shape : int or tuple or None
        Either an integer ``width`` or a tuple ``(width, signed)`` specifying the number of bits
        in this constant and whether it is signed (can represent negative values).
        ``shape`` defaults to the minimum possible width and signedness of ``value``.

    Attributes
    ----------
    width : int
    signed : bool
    """
    src_loc = None

    # TODO(amaranth-0.5): remove
    @staticmethod
    @deprecated("instead of `Const.normalize(value, shape)`, use `Const(value, shape).value`")
    def normalize(value, shape):
        mask = (1 << shape.width) - 1
        value &= mask
        if shape.signed and value >> (shape.width - 1):
            value |= ~mask
        return value

    @staticmethod
    def cast(obj):
        """Converts ``obj`` to an Amaranth constant.

        First, ``obj`` is converted to a value using :meth:`Value.cast`. If it is a constant, it
        is returned. If it is a constant-castable expression, it is evaluated and returned.
        Otherwise, :exn:`TypeError` is raised.
        """
        obj = Value.cast(obj)
        if type(obj) is Const:
            return obj
        elif type(obj) is Cat:
            value = 0
            width = 0
            for part in obj.parts:
                const  = Const.cast(part)
                value |= const.value << width
                width += len(const)
            return Const(value, width)
        else:
            raise TypeError("Value {!r} cannot be converted to an Amaranth constant".format(obj))

    def __init__(self, value, shape=None, *, src_loc_at=0):
        # We deliberately do not call Value.__init__ here.
        self.value = int(value)
        if shape is None:
            shape = Shape(bits_for(self.value), signed=self.value < 0)
        elif isinstance(shape, int):
            shape = Shape(shape, signed=self.value < 0)
        else:
            if isinstance(shape, range) and self.value == shape.stop:
                warnings.warn(
                    message="Value {!r} equals the non-inclusive end of the constant "
                            "shape {!r}; this is likely an off-by-one error"
                            .format(self.value, shape),
                    category=SyntaxWarning,
                    stacklevel=2)
            shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
        self.width  = shape.width
        self.signed = shape.signed
        if self.signed and self.value >> (self.width - 1) & 1:
            self.value |= -(1 << self.width)
        else:
            self.value &= (1 << self.width) - 1

    def shape(self):
        return Shape(self.width, self.signed)

    def _rhs_signals(self):
        return SignalSet()

    def __repr__(self):
        return "(const {}'{}d{})".format(self.width, "s" if self.signed else "", self.value)


C = Const  # shorthand


class AnyValue(Value, DUID):
    def __init__(self, shape, *, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
        self.width  = shape.width
        self.signed = shape.signed

    def shape(self):
        return Shape(self.width, self.signed)

    def _rhs_signals(self):
        return SignalSet()


@final
class AnyConst(AnyValue):
    def __repr__(self):
        return "(anyconst {}'{})".format(self.width, "s" if self.signed else "")


@final
class AnySeq(AnyValue):
    def __repr__(self):
        return "(anyseq {}'{})".format(self.width, "s" if self.signed else "")


@final
class Operator(Value):
    def __init__(self, operator, operands, *, src_loc_at=0):
        super().__init__(src_loc_at=1 + src_loc_at)
        self.operator = operator
        self.operands = [Value.cast(op) for op in operands]

    def shape(self):
        def _bitwise_binary_shape(a_shape, b_shape):
            if not a_shape.signed and not b_shape.signed:
                # both operands unsigned
                return unsigned(max(a_shape.width, b_shape.width))
            elif a_shape.signed and b_shape.signed:
                # both operands signed
                return signed(max(a_shape.width, b_shape.width))
            elif not a_shape.signed and b_shape.signed:
                # first operand unsigned (add sign bit), second operand signed
                return signed(max(a_shape.width + 1, b_shape.width))
            else:
                # first signed, second operand unsigned (add sign bit)
                return signed(max(a_shape.width, b_shape.width + 1))

        op_shapes = list(map(lambda x: x.shape(), self.operands))
        if len(op_shapes) == 1:
            a_shape, = op_shapes
            if self.operator in ("+", "~"):
                return Shape(a_shape.width, a_shape.signed)
            if self.operator == "-":
                return Shape(a_shape.width + 1, True)
            if self.operator in ("b", "r|", "r&", "r^"):
                return Shape(1, False)
            if self.operator == "u":
                return Shape(a_shape.width, False)
            if self.operator == "s":
                return Shape(a_shape.width, True)
        elif len(op_shapes) == 2:
            a_shape, b_shape = op_shapes
            if self.operator == "+":
                o_shape = _bitwise_binary_shape(*op_shapes)
                return Shape(o_shape.width + 1, o_shape.signed)
            if self.operator == "-":
                o_shape = _bitwise_binary_shape(*op_shapes)
                return Shape(o_shape.width + 1, True)
            if self.operator == "*":
                return Shape(a_shape.width + b_shape.width, a_shape.signed or b_shape.signed)
            if self.operator == "//":
                return Shape(a_shape.width + b_shape.signed, a_shape.signed or b_shape.signed)
            if self.operator == "%":
                return Shape(b_shape.width, b_shape.signed)
            if self.operator in ("<", "<=", "==", "!=", ">", ">="):
                return Shape(1, False)
            if self.operator in ("&", "^", "|"):
                return _bitwise_binary_shape(*op_shapes)
            if self.operator == "<<":
                assert not b_shape.signed
                return Shape(a_shape.width + 2 ** b_shape.width - 1, a_shape.signed)
            if self.operator == ">>":
                assert not b_shape.signed
                return Shape(a_shape.width, a_shape.signed)
        elif len(op_shapes) == 3:
            if self.operator == "m":
                s_shape, a_shape, b_shape = op_shapes
                return _bitwise_binary_shape(a_shape, b_shape)
        raise NotImplementedError("Operator {}/{} not implemented"
                                  .format(self.operator, len(op_shapes))) # :nocov:

    def _lhs_signals(self):
        if self.operator in ("u", "s"):
            return union(op._lhs_signals() for op in self.operands)
        return super()._lhs_signals()

    def _rhs_signals(self):
        return union(op._rhs_signals() for op in self.operands)

    def __repr__(self):
        return "({} {})".format(self.operator, " ".join(map(repr, self.operands)))


def Mux(sel, val1, val0):
    """Choose between two values.

    Parameters
    ----------
    sel : Value, in
        Selector.
    val1 : Value, in
    val0 : Value, in
        Input values.

    Returns
    -------
    Value, out
        Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
    """
    return Operator("m", [sel, val1, val0])


@final
class Slice(Value):
    def __init__(self, value, start, stop, *, src_loc_at=0):
        if not isinstance(start, int):
            raise TypeError("Slice start must be an integer, not {!r}".format(start))
        if not isinstance(stop, int):
            raise TypeError("Slice stop must be an integer, not {!r}".format(stop))

        value = Value.cast(value)
        n = len(value)
        if start not in range(-n, n+1):
            raise IndexError("Cannot start slice {} bits into {}-bit value".format(start, n))
        if start < 0:
            start += n
        if stop not in range(-n, n+1):
            raise IndexError("Cannot stop slice {} bits into {}-bit value".format(stop, n))
        if stop < 0:
            stop += n
        if start > stop:
            raise IndexError("Slice start {} must be less than slice stop {}".format(start, stop))

        super().__init__(src_loc_at=src_loc_at)
        self.value = value
        self.start = int(start)
        self.stop  = int(stop)

    def shape(self):
        return Shape(self.stop - self.start)

    def _lhs_signals(self):
        return self.value._lhs_signals()

    def _rhs_signals(self):
        return self.value._rhs_signals()

    def __repr__(self):
        return "(slice {} {}:{})".format(repr(self.value), self.start, self.stop)


@final
class Part(Value):
    def __init__(self, value, offset, width, stride=1, *, src_loc_at=0):
        if not isinstance(width, int) or width < 0:
            raise TypeError("Part width must be a non-negative integer, not {!r}".format(width))
        if not isinstance(stride, int) or stride <= 0:
            raise TypeError("Part stride must be a positive integer, not {!r}".format(stride))

        super().__init__(src_loc_at=src_loc_at)
        self.value  = Value.cast(value)
        self.offset = Value.cast(offset)
        self.width  = width
        self.stride = stride

    def shape(self):
        return Shape(self.width)

    def _lhs_signals(self):
        return self.value._lhs_signals()

    def _rhs_signals(self):
        return self.value._rhs_signals() | self.offset._rhs_signals()

    def __repr__(self):
        return "(part {} {} {} {})".format(repr(self.value), repr(self.offset),
                                           self.width, self.stride)


@final
class Cat(Value):
    """Concatenate values.

    Form a compound ``Value`` from several smaller ones by concatenation.
    The first argument occupies the lower bits of the result.
    The return value can be used on either side of an assignment, that
    is, the concatenated value can be used as an argument on the RHS or
    as a target on the LHS. If it is used on the LHS, it must solely
    consist of ``Signal`` s, slices of ``Signal`` s, and other concatenations
    meeting these properties. The bit length of the return value is the sum of
    the bit lengths of the arguments::

        len(Cat(args)) == sum(len(arg) for arg in args)

    Parameters
    ----------
    *args : Values or iterables of Values, inout
        ``Value`` s to be concatenated.

    Returns
    -------
    Value, inout
        Resulting ``Value`` obtained by concatenation.
    """
    def __init__(self, *args, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        self.parts = []
        for index, arg in enumerate(flatten(args)):
            if isinstance(arg, Enum) and (not isinstance(type(arg), ShapeCastable) or
                                          not hasattr(arg, "_amaranth_shape_")):
                warnings.warn("Argument #{} of Cat() is an enumerated value {!r} without "
                              "a defined shape used in bit vector context; define the enumeration "
                              "by inheriting from the class in amaranth.lib.enum and specifying "
                              "the 'shape=' keyword argument"
                              .format(index + 1, arg),
                              SyntaxWarning, stacklevel=2 + src_loc_at)
            if isinstance(arg, int) and not isinstance(arg, Enum) and arg not in [0, 1]:
                warnings.warn("Argument #{} of Cat() is a bare integer {} used in bit vector "
                              "context; specify the width explicitly using C({}, {})"
                              .format(index + 1, arg, arg, bits_for(arg)),
                              SyntaxWarning, stacklevel=2 + src_loc_at)
            self.parts.append(Value.cast(arg))

    def shape(self):
        return Shape(sum(len(part) for part in self.parts))

    def _lhs_signals(self):
        return union((part._lhs_signals() for part in self.parts), start=SignalSet())

    def _rhs_signals(self):
        return union((part._rhs_signals() for part in self.parts), start=SignalSet())

    def __repr__(self):
        return "(cat {})".format(" ".join(map(repr, self.parts)))


# TODO(amaranth-0.5): remove
@deprecated("instead of `Repl(value, count)`, use `value.replicate(count)`")
def Repl(value, count):
    """Replicate a value

    An input value is replicated (repeated) several times
    to be used on the RHS of assignments::

        len(Repl(s, n)) == len(s) * n

    Parameters
    ----------
    value : Value, in
        Input value to be replicated.
    count : int
        Number of replications.

    Returns
    -------
    Value, out
        Replicated value.
    """
    if isinstance(value, int) and value not in [0, 1]:
        warnings.warn("Value argument of Repl() is a bare integer {} used in bit vector "
                        "context; consider specifying explicit width using C({}, {}) instead"
                        .format(value, value, bits_for(value)),
                        SyntaxWarning, stacklevel=3)

    return Value.cast(value).replicate(count)


class _SignalMeta(ABCMeta):
    def __call__(cls, shape=None, src_loc_at=0, **kwargs):
        signal = super().__call__(shape, **kwargs, src_loc_at=src_loc_at + 1)
        if isinstance(shape, ShapeCastable):
            return shape(signal)
        return signal


# @final
class Signal(Value, DUID, metaclass=_SignalMeta):
    """A varying integer value.

    Parameters
    ----------
    shape : ``Shape``-castable object or None
        Specification for the number of bits in this ``Signal`` and its signedness (whether it
        can represent negative values). See ``Shape.cast`` for details.
        If not specified, ``shape`` defaults to 1-bit and non-signed.
    name : str
        Name hint for this signal. If ``None`` (default) the name is inferred from the variable
        name this ``Signal`` is assigned to.
    reset : int or integral Enum
        Reset (synchronous) or default (combinatorial) value.
        When this ``Signal`` is assigned to in synchronous context and the corresponding clock
        domain is reset, the ``Signal`` assumes the given value. When this ``Signal`` is unassigned
        in combinatorial context (due to conditional assignments not being taken), the ``Signal``
        assumes its ``reset`` value. Defaults to 0.
    reset_less : bool
        If ``True``, do not generate reset logic for this ``Signal`` in synchronous statements.
        The ``reset`` value is only used as a combinatorial default or as the initial value.
        Defaults to ``False``.
    attrs : dict
        Dictionary of synthesis attributes.
    decoder : function or Enum
        A function converting integer signal values to human-readable strings (e.g. FSM state
        names). If an ``Enum`` subclass is passed, it is concisely decoded using format string
        ``"{0.name:}/{0.value:}"``, or a number if the signal value is not a member of
        the enumeration.

    Attributes
    ----------
    width : int
    signed : bool
    name : str
    reset : int
    reset_less : bool
    attrs : dict
    decoder : function
    """

    def __init__(self, shape=None, *, name=None, reset=None, reset_less=False,
                 attrs=None, decoder=None, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)

        if name is not None and not isinstance(name, str):
            raise TypeError("Name must be a string, not {!r}".format(name))
        self.name = name or tracer.get_var_name(depth=2 + src_loc_at, default="$signal")

        orig_shape = shape
        if shape is None:
            shape = unsigned(1)
        else:
            shape = Shape.cast(shape, src_loc_at=1 + src_loc_at)
        self.width  = shape.width
        self.signed = shape.signed

        orig_reset = reset
        if isinstance(orig_shape, ShapeCastable):
            try:
                reset = Const.cast(orig_shape.const(reset))
            except Exception:
                raise TypeError("Reset value must be a constant initializer of {!r}"
                                .format(orig_shape))
            if reset.shape() != Shape.cast(orig_shape):
                raise ValueError("Constant returned by {!r}.const() must have the shape that "
                                 "it casts to, {!r}, and not {!r}"
                                 .format(orig_shape, Shape.cast(orig_shape),
                                         reset.shape()))
        else:
            try:
                reset = Const.cast(reset or 0)
            except TypeError:
                raise TypeError("Reset value must be a constant-castable expression, not {!r}"
                                .format(orig_reset))
        if orig_reset not in (None, 0, -1): # Avoid false positives for all-zeroes and all-ones
            if reset.shape().signed and not self.signed:
                warnings.warn(
                    message="Reset value {!r} is signed, but the signal shape is {!r}"
                            .format(orig_reset, shape),
                    category=SyntaxWarning,
                    stacklevel=2)
            elif (reset.shape().width > self.width or
                  reset.shape().width == self.width and
                    self.signed and not reset.shape().signed):
                warnings.warn(
                    message="Reset value {!r} will be truncated to the signal shape {!r}"
                            .format(orig_reset, shape),
                    category=SyntaxWarning,
                    stacklevel=2)
        self.reset = reset.value
        self.reset_less = bool(reset_less)

        if isinstance(orig_shape, range) and self.reset == orig_shape.stop:
            warnings.warn(
                message="Reset value {!r} equals the non-inclusive end of the signal "
                        "shape {!r}; this is likely an off-by-one error"
                        .format(self.reset, orig_shape),
                category=SyntaxWarning,
                stacklevel=2)

        self.attrs = OrderedDict(() if attrs is None else attrs)

        if decoder is None and isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
            decoder = orig_shape
        if isinstance(decoder, type) and issubclass(decoder, Enum):
            def enum_decoder(value):
                try:
                    return "{0.name:}/{0.value:}".format(decoder(value))
                except ValueError:
                    return str(value)
            self.decoder = enum_decoder
            self._enum_class = decoder
        else:
            self.decoder = decoder
            self._enum_class = None

    # Not a @classmethod because amaranth.compat requires it.
    @staticmethod
    def like(other, *, name=None, name_suffix=None, src_loc_at=0, **kwargs):
        """Create Signal based on another.

        Parameters
        ----------
        other : Value
            Object to base this Signal on.
        """
        if name is not None:
            new_name = str(name)
        elif name_suffix is not None:
            new_name = other.name + str(name_suffix)
        else:
            new_name = tracer.get_var_name(depth=2 + src_loc_at, default="$like")
        if isinstance(other, ValueCastable):
            shape = other.shape()
        else:
            shape = Value.cast(other).shape()
        kw = dict(shape=shape, name=new_name)
        if isinstance(other, Signal):
            kw.update(reset=other.reset, reset_less=other.reset_less,
                      attrs=other.attrs, decoder=other.decoder)
        kw.update(kwargs)
        return Signal(**kw, src_loc_at=1 + src_loc_at)

    def shape(self):
        return Shape(self.width, self.signed)

    def _lhs_signals(self):
        return SignalSet((self,))

    def _rhs_signals(self):
        return SignalSet((self,))

    def __repr__(self):
        return "(sig {})".format(self.name)


@final
class ClockSignal(Value):
    """Clock signal for a clock domain.

    Any ``ClockSignal`` is equivalent to ``cd.clk`` for a clock domain with the corresponding name.
    All of these signals ultimately refer to the same signal, but they can be manipulated
    independently of the clock domain, even before the clock domain is created.

    Parameters
    ----------
    domain : str
        Clock domain to obtain a clock signal for. Defaults to ``"sync"``.
    """
    def __init__(self, domain="sync", *, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        if not isinstance(domain, str):
            raise TypeError("Clock domain name must be a string, not {!r}".format(domain))
        if domain == "comb":
            raise ValueError("Domain '{}' does not have a clock".format(domain))
        self.domain = domain

    def shape(self):
        return Shape(1)

    def _lhs_signals(self):
        return SignalSet((self,))

    def _rhs_signals(self):
        raise NotImplementedError("ClockSignal must be lowered to a concrete signal") # :nocov:

    def __repr__(self):
        return "(clk {})".format(self.domain)


@final
class ResetSignal(Value):
    """Reset signal for a clock domain.

    Any ``ResetSignal`` is equivalent to ``cd.rst`` for a clock domain with the corresponding name.
    All of these signals ultimately refer to the same signal, but they can be manipulated
    independently of the clock domain, even before the clock domain is created.

    Parameters
    ----------
    domain : str
        Clock domain to obtain a reset signal for. Defaults to ``"sync"``.
    allow_reset_less : bool
        If the clock domain is reset-less, act as a constant ``0`` instead of reporting an error.
    """
    def __init__(self, domain="sync", allow_reset_less=False, *, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        if not isinstance(domain, str):
            raise TypeError("Clock domain name must be a string, not {!r}".format(domain))
        if domain == "comb":
            raise ValueError("Domain '{}' does not have a reset".format(domain))
        self.domain = domain
        self.allow_reset_less = allow_reset_less

    def shape(self):
        return Shape(1)

    def _lhs_signals(self):
        return SignalSet((self,))

    def _rhs_signals(self):
        raise NotImplementedError("ResetSignal must be lowered to a concrete signal") # :nocov:

    def __repr__(self):
        return "(rst {})".format(self.domain)


class Array(MutableSequence):
    """Addressable multiplexer.

    An array is similar to a ``list`` that can also be indexed by ``Value``s; indexing by an integer or a slice works the same as for Python lists, but indexing by a ``Value`` results
    in a proxy.

    The array proxy can be used as an ordinary ``Value``, i.e. participate in calculations and
    assignments, provided that all elements of the array are values. The array proxy also supports
    attribute access and further indexing, each returning another array proxy; this means that
    the results of indexing into arrays, arrays of records, and arrays of arrays can all
    be used as first-class values.

    It is an error to change an array or any of its elements after an array proxy was created.
    Changing the array directly will raise an exception. However, it is not possible to detect
    the elements being modified; if an element's attribute or element is modified after the proxy
    for it has been created, the proxy will refer to stale data.

    Examples
    --------

    Simple array::

        gpios = Array(Signal() for _ in range(10))
        with m.If(bus.we):
            m.d.sync += gpios[bus.addr].eq(bus.w_data)
        with m.Else():
            m.d.sync += bus.r_data.eq(gpios[bus.addr])

    Multidimensional array::

        mult = Array(Array(x * y for y in range(10)) for x in range(10))
        a = Signal.range(10)
        b = Signal.range(10)
        r = Signal(8)
        m.d.comb += r.eq(mult[a][b])

    Array of records::

        layout = [
            ("r_data", 16),
            ("r_en",   1),
        ]
        buses  = Array(Record(layout) for busno in range(4))
        master = Record(layout)
        m.d.comb += [
            buses[sel].r_en.eq(master.r_en),
            master.r_data.eq(buses[sel].r_data),
        ]
    """
    def __init__(self, iterable=()):
        self._inner    = list(iterable)
        self._proxy_at = None
        self._mutable  = True

    def __getitem__(self, index):
        if isinstance(index, Value):
            if self._mutable:
                self._proxy_at = tracer.get_src_loc()
                self._mutable  = False
            return ArrayProxy(self, index)
        else:
            return self._inner[index]

    def __len__(self):
        return len(self._inner)

    def _check_mutability(self):
        if not self._mutable:
            raise ValueError("Array can no longer be mutated after it was indexed with a value "
                             "at {}:{}".format(*self._proxy_at))

    def __setitem__(self, index, value):
        self._check_mutability()
        self._inner[index] = value

    def __delitem__(self, index):
        self._check_mutability()
        del self._inner[index]

    def insert(self, index, value):
        self._check_mutability()
        self._inner.insert(index, value)

    def __repr__(self):
        return "(array{} [{}])".format(" mutable" if self._mutable else "",
                                       ", ".join(map(repr, self._inner)))


@final
class ArrayProxy(Value):
    def __init__(self, elems, index, *, src_loc_at=0):
        super().__init__(src_loc_at=1 + src_loc_at)
        self.elems = elems
        self.index = Value.cast(index)

    def __getattr__(self, attr):
        return ArrayProxy([getattr(elem, attr) for elem in self.elems], self.index)

    def __getitem__(self, index):
        return ArrayProxy([        elem[index] for elem in self.elems], self.index)

    def _iter_as_values(self):
        return (Value.cast(elem) for elem in self.elems)

    def shape(self):
        unsigned_width = signed_width = 0
        has_unsigned = has_signed = False
        for elem_shape in (elem.shape() for elem in self._iter_as_values()):
            if elem_shape.signed:
                has_signed = True
                signed_width = max(signed_width, elem_shape.width)
            else:
                has_unsigned = True
                unsigned_width = max(unsigned_width, elem_shape.width)
        # The shape of the proxy must be such that it preserves the mathematical value of the array
        # elements. I.e., shape-wise, an array proxy must be identical to an equivalent mux tree.
        # To ensure this holds, if the array contains both signed and unsigned values, make sure
        # that every unsigned value is zero-extended by at least one bit.
        if has_signed and has_unsigned and unsigned_width >= signed_width:
            # Array contains both signed and unsigned values, and at least one of the unsigned
            # values won't be zero-extended otherwise.
            return signed(unsigned_width + 1)
        else:
            # Array contains values of the same signedness, or else all of the unsigned values
            # are zero-extended.
            return Shape(max(unsigned_width, signed_width), has_signed)

    def _lhs_signals(self):
        signals = union((elem._lhs_signals() for elem in self._iter_as_values()),
                        start=SignalSet())
        return signals

    def _rhs_signals(self):
        signals = union((elem._rhs_signals() for elem in self._iter_as_values()),
                        start=SignalSet())
        return self.index._rhs_signals() | signals

    def __repr__(self):
        return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)


class ValueCastable:
    """Interface of user-defined objects that can be cast to :class:`Value` s.

    An object deriving from :class:`ValueCastable`` is automatically converted to a :class:`Value`
    when it is used in a context where a :class:`Value`` is expected. Such objects can implement
    different or richer semantics than what is supported by the core Amaranth language, yet still
    be transparently used with it as long as the final underlying representation is a single
    Amaranth :class:`Value`. These objects also need not commit to a specific representation until
    they are converted to a concrete Amaranth value.

    Note that it is necessary to ensure that Amaranth's view of representation of all values stays
    internally consistent. The class deriving from :class:`ValueCastable`` must decorate
    the :meth:`as_value` method with the :meth:`lowermethod` decorator, which ensures that all
    calls to :meth:`as_value` return the same :class:`Value` representation. If the class deriving
    from :class:`ValueCastable` is mutable, it is up to the user to ensure that it is not mutated
    in a way that changes its representation after the first call to :meth:`as_value`.
    """
    def __init_subclass__(cls, **kwargs):
        if not hasattr(cls, "as_value"):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must override "
                            "the `as_value` method")
        if not hasattr(cls, "shape"):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must override "
                            "the `shape` method")
        if not hasattr(cls.as_value, "_ValueCastable__memoized"):
            raise TypeError(f"Class '{cls.__name__}' deriving from `ValueCastable` must decorate "
                            "the `as_value` method with the `ValueCastable.lowermethod` decorator")

    @staticmethod
    def lowermethod(func):
        """Decorator to memoize lowering methods.

        Ensures the decorated method is called only once, with subsequent method calls returning
        the object returned by the first first method call.

        This decorator is required to decorate the ``as_value`` method of ``ValueCastable``
        subclasses. This is to ensure that Amaranth's view of representation of all values stays
        internally consistent.
        """
        @functools.wraps(func)
        def wrapper_memoized(self, *args, **kwargs):
            # Use `in self.__dict__` instead of `hasattr` to avoid interfering with custom
            # `__getattr__` implementations.
            if not "_ValueCastable__lowered_to" in self.__dict__:
                self.__lowered_to = func(self, *args, **kwargs)
            return self.__lowered_to
        wrapper_memoized.__memoized = True
        return wrapper_memoized


# TODO(amaranth-0.5): remove
@final
class Sample(Value):
    """Value from the past.

    A ``Sample`` of an expression is equal to the value of the expression ``clocks`` clock edges
    of the ``domain`` clock back. If that moment is before the beginning of time, it is equal
    to the value of the expression calculated as if each signal had its reset value.
    """
    @deprecated("instead of using `Sample`, create a register explicitly")
    def __init__(self, expr, clocks, domain, *, src_loc_at=0):
        super().__init__(src_loc_at=1 + src_loc_at)
        self.value  = Value.cast(expr)
        self.clocks = int(clocks)
        self.domain = domain
        if not isinstance(self.value, (Const, Signal, ClockSignal, ResetSignal, Initial)):
            raise TypeError("Sampled value must be a signal or a constant, not {!r}"
                            .format(self.value))
        if self.clocks < 0:
            raise ValueError("Cannot sample a value {} cycles in the future"
                             .format(-self.clocks))
        if not (self.domain is None or isinstance(self.domain, str)):
            raise TypeError("Domain name must be a string or None, not {!r}"
                            .format(self.domain))

    def shape(self):
        return self.value.shape()

    def _rhs_signals(self):
        return SignalSet((self,))

    def __repr__(self):
        return "(sample {!r} @ {}[{}])".format(
            self.value, "<default>" if self.domain is None else self.domain, self.clocks)


# TODO(amaranth-0.5): remove
@deprecated("instead of using `Past`, create a register explicitly")
def Past(expr, clocks=1, domain=None):
    with _ignore_deprecated():
        return Sample(expr, clocks, domain)


# TODO(amaranth-0.5): remove
@deprecated("instead of using `Stable`, create registers and comparisons explicitly")
def Stable(expr, clocks=0, domain=None):
    with _ignore_deprecated():
        return Sample(expr, clocks + 1, domain) == Sample(expr, clocks, domain)


# TODO(amaranth-0.5): remove
@deprecated("instead of using `Rose`, create registers and comparisons explicitly")
def Rose(expr, clocks=0, domain=None):
    with _ignore_deprecated():
        return ~Sample(expr, clocks + 1, domain) & Sample(expr, clocks, domain)


# TODO(amaranth-0.5): remove
@deprecated("instead of using `Fell`, create registers and comparisons explicitly")
def Fell(expr, clocks=0, domain=None):
    with _ignore_deprecated():
        return Sample(expr, clocks + 1, domain) & ~Sample(expr, clocks, domain)


@final
class Initial(Value):
    """Start indicator, for model checking.

    An ``Initial`` signal is ``1`` at the first cycle of model checking, and ``0`` at any other.
    """
    def __init__(self, *, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)

    def shape(self):
        return Shape(1)

    def _rhs_signals(self):
        return SignalSet((self,))

    def __repr__(self):
        return "(initial)"


class _StatementList(list):
    def __repr__(self):
        return "({})".format(" ".join(map(repr, self)))


class Statement:
    def __init__(self, *, src_loc_at=0):
        self.src_loc = tracer.get_src_loc(1 + src_loc_at)

    @staticmethod
    def cast(obj):
        if isinstance(obj, Iterable):
            return _StatementList(list(chain.from_iterable(map(Statement.cast, obj))))
        else:
            if isinstance(obj, Statement):
                return _StatementList([obj])
            else:
                raise TypeError("Object {!r} is not an Amaranth statement".format(obj))


@final
class Assign(Statement):
    def __init__(self, lhs, rhs, *, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        self.lhs = Value.cast(lhs)
        self.rhs = Value.cast(rhs)

    def _lhs_signals(self):
        return self.lhs._lhs_signals()

    def _rhs_signals(self):
        return self.lhs._rhs_signals() | self.rhs._rhs_signals()

    def __repr__(self):
        return "(eq {!r} {!r})".format(self.lhs, self.rhs)


class UnusedProperty(UnusedMustUse):
    pass


class Property(Statement, MustUse):
    _MustUse__warning = UnusedProperty

    def __init__(self, test, *, _check=None, _en=None, name=None, src_loc_at=0):
        super().__init__(src_loc_at=src_loc_at)
        self.test   = Value.cast(test)
        self._check = _check
        self._en    = _en
        self.name   = name
        if not isinstance(self.name, str) and self.name is not None:
            raise TypeError("Property name must be a string or None, not {!r}"
                            .format(self.name))
        if self._check is None:
            self._check = Signal(reset_less=True, name="${}$check".format(self._kind))
            self._check.src_loc = self.src_loc
        if _en is None:
            self._en = Signal(reset_less=True, name="${}$en".format(self._kind))
            self._en.src_loc = self.src_loc

    def _lhs_signals(self):
        return SignalSet((self._en, self._check))

    def _rhs_signals(self):
        return self.test._rhs_signals()

    def __repr__(self):
        if self.name is not None:
            return "({}: {} {!r})".format(self.name, self._kind, self.test)    
        return "({} {!r})".format(self._kind, self.test)


@final
class Assert(Property):
    _kind = "assert"


@final
class Assume(Property):
    _kind = "assume"


@final
class Cover(Property):
    _kind = "cover"


# @final
class Switch(Statement):
    def __init__(self, test, cases, *, src_loc=None, src_loc_at=0, case_src_locs={}):
        if src_loc is None:
            super().__init__(src_loc_at=src_loc_at)
        else:
            # Switch is a bit special in terms of location tracking because it is usually created
            # long after the control has left the statement that directly caused its creation.
            self.src_loc = src_loc
        # Switch is also a bit special in that its parts also have location information. It can't
        # be automatically traced, so whatever constructs a Switch may optionally provide it.
        self.case_src_locs = {}

        self.test  = Value.cast(test)
        self.cases = OrderedDict()
        for orig_keys, stmts in cases.items():
            # Map: None -> (); key -> (key,); (key...) -> (key...)
            keys = orig_keys
            if keys is None:
                keys = ()
            if not isinstance(keys, tuple):
                keys = (keys,)
            # Map: 2 -> "0010"; "0010" -> "0010"
            new_keys = ()
            key_mask = (1 << len(self.test)) - 1
            for key in keys:
                if isinstance(key, str):
                    key = "".join(key.split()) # remove whitespace
                elif isinstance(key, int):
                    key = format(key & key_mask, "b").rjust(len(self.test), "0")
                elif isinstance(key, Enum):
                    key = format(key.value & key_mask, "b").rjust(len(self.test), "0")
                else:
                    raise TypeError("Object {!r} cannot be used as a switch key"
                                    .format(key))
                assert len(key) == len(self.test)
                new_keys = (*new_keys, key)
            if not isinstance(stmts, Iterable):
                stmts = [stmts]
            self.cases[new_keys] = Statement.cast(stmts)
            if orig_keys in case_src_locs:
                self.case_src_locs[new_keys] = case_src_locs[orig_keys]

    def _lhs_signals(self):
        signals = union((s._lhs_signals() for ss in self.cases.values() for s in ss),
                        start=SignalSet())
        return signals

    def _rhs_signals(self):
        signals = union((s._rhs_signals() for ss in self.cases.values() for s in ss),
                        start=SignalSet())
        return self.test._rhs_signals() | signals

    def __repr__(self):
        def case_repr(keys, stmts):
            stmts_repr = " ".join(map(repr, stmts))
            if keys == ():
                return "(default {})".format(stmts_repr)
            elif len(keys) == 1:
                return "(case {} {})".format(keys[0], stmts_repr)
            else:
                return "(case ({}) {})".format(" ".join(keys), stmts_repr)
        case_reprs = [case_repr(keys, stmts) for keys, stmts in self.cases.items()]
        return "(switch {!r} {})".format(self.test, " ".join(case_reprs))


class _MappedKeyCollection(metaclass=ABCMeta):
    @abstractmethod
    def _map_key(self, key):
        pass # :nocov:

    @abstractmethod
    def _unmap_key(self, key):
        pass # :nocov:


class _MappedKeyDict(MutableMapping, _MappedKeyCollection):
    def __init__(self, pairs=()):
        self._storage = OrderedDict()
        for key, value in pairs:
            self[key] = value

    def __getitem__(self, key):
        key = None if key is None else self._map_key(key)
        return self._storage[key]

    def __setitem__(self, key, value):
        key = None if key is None else self._map_key(key)
        self._storage[key] = value

    def __delitem__(self, key):
        key = None if key is None else self._map_key(key)
        del self._storage[key]

    def __iter__(self):
        for key in self._storage:
            if key is None:
                yield None
            else:
                yield self._unmap_key(key)

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return False
        if len(self) != len(other):
            return False
        for ak, bk in zip(sorted(self._storage), sorted(other._storage)):
            if ak != bk:
                return False
            if self._storage[ak] != other._storage[bk]:
                return False
        return True

    def __len__(self):
        return len(self._storage)

    def __repr__(self):
        pairs = ["({!r}, {!r})".format(k, v) for k, v in self.items()]
        return "{}.{}([{}])".format(type(self).__module__, type(self).__name__,
                                    ", ".join(pairs))


class _MappedKeySet(MutableSet, _MappedKeyCollection):
    def __init__(self, elements=()):
        self._storage = OrderedDict()
        for elem in elements:
            self.add(elem)

    def add(self, value):
        self._storage[self._map_key(value)] = None

    def update(self, values):
        for value in values:
            self.add(value)

    def discard(self, value):
        if value in self:
            del self._storage[self._map_key(value)]

    def __contains__(self, value):
        return self._map_key(value) in self._storage

    def __iter__(self):
        for key in [k for k in self._storage]:
            yield self._unmap_key(key)

    def __len__(self):
        return len(self._storage)

    def __repr__(self):
        return "{}.{}({})".format(type(self).__module__, type(self).__name__,
                                  ", ".join(repr(x) for x in self))


class ValueKey:
    def __init__(self, value):
        self.value = Value.cast(value)
        if isinstance(self.value, Const):
            self._hash = hash(self.value.value)
        elif isinstance(self.value, (Signal, AnyValue)):
            self._hash = hash(self.value.duid)
        elif isinstance(self.value, (ClockSignal, ResetSignal)):
            self._hash = hash(self.value.domain)
        elif isinstance(self.value, Operator):
            self._hash = hash((self.value.operator,
                               tuple(ValueKey(o) for o in self.value.operands)))
        elif isinstance(self.value, Slice):
            self._hash = hash((ValueKey(self.value.value), self.value.start, self.value.stop))
        elif isinstance(self.value, Part):
            self._hash = hash((ValueKey(self.value.value), ValueKey(self.value.offset),
                              self.value.width, self.value.stride))
        elif isinstance(self.value, Cat):
            self._hash = hash(tuple(ValueKey(o) for o in self.value.parts))
        elif isinstance(self.value, ArrayProxy):
            self._hash = hash((ValueKey(self.value.index),
                              tuple(ValueKey(e) for e in self.value._iter_as_values())))
        elif isinstance(self.value, Sample):
            self._hash = hash((ValueKey(self.value.value), self.value.clocks, self.value.domain))
        elif isinstance(self.value, Initial):
            self._hash = 0
        else: # :nocov:
            raise TypeError("Object {!r} cannot be used as a key in value collections"
                            .format(self.value))

    def __hash__(self):
        return self._hash

    def __eq__(self, other):
        if type(other) is not ValueKey:
            return False
        if type(self.value) is not type(other.value):
            return False

        if isinstance(self.value, Const):
            return self.value.value == other.value.value and self.value.width == other.value.width
        elif isinstance(self.value, (Signal, AnyValue)):
            return self.value is other.value
        elif isinstance(self.value, (ClockSignal, ResetSignal)):
            return self.value.domain == other.value.domain
        elif isinstance(self.value, Operator):
            return (self.value.operator == other.value.operator and
                    len(self.value.operands) == len(other.value.operands) and
                    all(ValueKey(a) == ValueKey(b)
                        for a, b in zip(self.value.operands, other.value.operands)))
        elif isinstance(self.value, Slice):
            return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                    self.value.start == other.value.start and
                    self.value.stop == other.value.stop)
        elif isinstance(self.value, Part):
            return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                    ValueKey(self.value.offset) == ValueKey(other.value.offset) and
                    self.value.width == other.value.width and
                    self.value.stride == other.value.stride)
        elif isinstance(self.value, Cat):
            return (len(self.value.parts) == len(other.value.parts) and
                    all(ValueKey(a) == ValueKey(b)
                        for a, b in zip(self.value.parts, other.value.parts)))
        elif isinstance(self.value, ArrayProxy):
            return (ValueKey(self.value.index) == ValueKey(other.value.index) and
                    len(self.value.elems) == len(other.value.elems) and
                    all(ValueKey(a) == ValueKey(b)
                        for a, b in zip(self.value._iter_as_values(),
                                        other.value._iter_as_values())))
        elif isinstance(self.value, Sample):
            return (ValueKey(self.value.value) == ValueKey(other.value.value) and
                    self.value.clocks == other.value.clocks and
                    self.value.domain == self.value.domain)
        elif isinstance(self.value, Initial):
            return True
        else: # :nocov:
            raise TypeError("Object {!r} cannot be used as a key in value collections"
                            .format(self.value))

    def __lt__(self, other):
        if not isinstance(other, ValueKey):
            return False
        if type(self.value) != type(other.value):
            return False

        if isinstance(self.value, Const):
            return self.value < other.value
        elif isinstance(self.value, (Signal, AnyValue)):
            return self.value.duid < other.value.duid
        elif isinstance(self.value, Slice):
            return (ValueKey(self.value.value) < ValueKey(other.value.value) and
                    self.value.start < other.value.start and
                    self.value.end < other.value.end)
        else: # :nocov:
            raise TypeError("Object {!r} cannot be used as a key in value collections")

    def __repr__(self):
        return "<{}.ValueKey {!r}>".format(__name__, self.value)


class ValueDict(_MappedKeyDict):
    _map_key   = ValueKey
    _unmap_key = lambda self, key: key.value


class ValueSet(_MappedKeySet):
    _map_key   = ValueKey
    _unmap_key = lambda self, key: key.value


class SignalKey:
    def __init__(self, signal):
        self.signal = signal
        if isinstance(signal, Signal):
            self._intern = (0, signal.duid)
        elif type(signal) is ClockSignal:
            self._intern = (1, signal.domain)
        elif type(signal) is ResetSignal:
            self._intern = (2, signal.domain)
        else:
            raise TypeError("Object {!r} is not an Amaranth signal".format(signal))

    def __hash__(self):
        return hash(self._intern)

    def __eq__(self, other):
        if type(other) is not SignalKey:
            return False
        return self._intern == other._intern

    def __lt__(self, other):
        if type(other) is not SignalKey:
            raise TypeError("Object {!r} cannot be compared to a SignalKey".format(other))
        return self._intern < other._intern

    def __repr__(self):
        return "<{}.SignalKey {!r}>".format(__name__, self.signal)


class SignalDict(_MappedKeyDict):
    _map_key   = SignalKey
    _unmap_key = lambda self, key: key.signal


class SignalSet(_MappedKeySet):
    _map_key   = SignalKey
    _unmap_key = lambda self, key: key.signal


# amaranth/hdl/cd.py

from .. import tracer
from .ast import Signal


__all__ = ["ClockDomain", "DomainError"]


class DomainError(Exception):
    pass


class ClockDomain:
    """Synchronous domain.

    Parameters
    ----------
    name : str or None
        Domain name. If ``None`` (the default) the name is inferred from the variable name this
        ``ClockDomain`` is assigned to (stripping any `"cd_"` prefix).
    reset_less : bool
        If ``True``, the domain does not use a reset signal. Registers within this domain are
        still all initialized to their reset state once, e.g. through Verilog `"initial"`
        statements.
    clk_edge : str
        The edge of the clock signal on which signals are sampled. Must be one of "pos" or "neg".
    async_reset : bool
        If ``True``, the domain uses an asynchronous reset, and registers within this domain
        are initialized to their reset state when reset level changes. Otherwise, registers
        are initialized to reset state at the next clock cycle when reset is asserted.
    local : bool
        If ``True``, the domain will propagate only downwards in the design hierarchy. Otherwise,
        the domain will propagate everywhere.

    Attributes
    ----------
    clk : Signal, inout
        The clock for this domain. Can be driven or used to drive other signals (preferably
        in combinatorial context).
    rst : Signal or None, inout
        Reset signal for this domain. Can be driven or used to drive.
    """

    @staticmethod
    def _name_for(domain_name, signal_name):
        if domain_name == "sync":
            return signal_name
        else:
            return "{}_{}".format(domain_name, signal_name)

    def __init__(self, name=None, *, clk_edge="pos", reset_less=False, async_reset=False,
                 local=False):
        if name is None:
            try:
                name = tracer.get_var_name()
            except tracer.NameNotFound:
                raise ValueError("Clock domain name must be specified explicitly")
        if name.startswith("cd_"):
            name = name[3:]
        if name == "comb":
            raise ValueError("Domain '{}' may not be clocked".format(name))

        if clk_edge not in ("pos", "neg"):
            raise ValueError("Domain clock edge must be one of 'pos' or 'neg', not {!r}"
                             .format(clk_edge))

        self.name = name

        self.clk = Signal(name=self._name_for(name, "clk"), src_loc_at=1)
        self.clk_edge = clk_edge

        if reset_less:
            self.rst = None
        else:
            self.rst = Signal(name=self._name_for(name, "rst"), src_loc_at=1)

        self.async_reset = async_reset

        self.local = local

    def rename(self, new_name):
        self.name = new_name
        self.clk.name = self._name_for(new_name, "clk")
        if self.rst is not None:
            self.rst.name = self._name_for(new_name, "rst")


# amaranth/hdl/dsl.py

from collections import OrderedDict
from contextlib import contextmanager, _GeneratorContextManager
from functools import wraps
from enum import Enum
import warnings
import sys

from .._utils import flatten, bits_for
from .. import tracer
from .ast import *
from .ir import *
from .cd import *
from .xfrm import *


__all__ = ["SyntaxError", "SyntaxWarning", "Module"]


class SyntaxError(Exception):
    pass


class SyntaxWarning(Warning):
    pass


class _ModuleBuilderProxy:
    def __init__(self, builder, depth):
        object.__setattr__(self, "_builder", builder)
        object.__setattr__(self, "_depth", depth)


class _ModuleBuilderDomain(_ModuleBuilderProxy):
    def __init__(self, builder, depth, domain):
        super().__init__(builder, depth)
        self._domain = domain

    def __iadd__(self, assigns):
        self._builder._add_statement(assigns, domain=self._domain, depth=self._depth)
        return self


class _ModuleBuilderDomains(_ModuleBuilderProxy):
    def __getattr__(self, name):
        if name == "submodules":
            warnings.warn("Using '<module>.d.{}' would add statements to clock domain {!r}; "
                          "did you mean <module>.{} instead?"
                          .format(name, name, name),
                          SyntaxWarning, stacklevel=2)
        if name == "comb":
            domain = None
        else:
            domain = name
        return _ModuleBuilderDomain(self._builder, self._depth, domain)

    def __getitem__(self, name):
        return self.__getattr__(name)

    def __setattr__(self, name, value):
        if name == "_depth":
            object.__setattr__(self, name, value)
        elif not isinstance(value, _ModuleBuilderDomain):
            raise AttributeError("Cannot assign 'd.{}' attribute; did you mean 'd.{} +='?"
                                 .format(name, name))

    def __setitem__(self, name, value):
        return self.__setattr__(name, value)


class _ModuleBuilderRoot:
    def __init__(self, builder, depth):
        self._builder = builder
        self.domain = self.d = _ModuleBuilderDomains(builder, depth)

    def __getattr__(self, name):
        if name in ("comb", "sync"):
            raise AttributeError("'{}' object has no attribute '{}'; did you mean 'd.{}'?"
                                 .format(type(self).__name__, name, name))
        raise AttributeError("'{}' object has no attribute '{}'"
                             .format(type(self).__name__, name))


class _ModuleBuilderSubmodules:
    def __init__(self, builder):
        object.__setattr__(self, "_builder", builder)

    def __iadd__(self, modules):
        for module in flatten([modules]):
            self._builder._add_submodule(module)
        return self

    def __setattr__(self, name, submodule):
        self._builder._add_submodule(submodule, name)

    def __setitem__(self, name, value):
        return self.__setattr__(name, value)

    def __getattr__(self, name):
        return self._builder._get_submodule(name)

    def __getitem__(self, name):
        return self.__getattr__(name)


class _ModuleBuilderDomainSet:
    def __init__(self, builder):
        object.__setattr__(self, "_builder", builder)

    def __iadd__(self, domains):
        for domain in flatten([domains]):
            if not isinstance(domain, ClockDomain):
                raise TypeError("Only clock domains may be added to `m.domains`, not {!r}"
                                .format(domain))
            self._builder._add_domain(domain)
        return self

    def __setattr__(self, name, domain):
        if not isinstance(domain, ClockDomain):
            raise TypeError("Only clock domains may be added to `m.domains`, not {!r}"
                            .format(domain))
        if domain.name != name:
            raise NameError("Clock domain name {!r} must match name in `m.domains.{} += ...` "
                            "syntax"
                            .format(domain.name, name))
        self._builder._add_domain(domain)


# It's not particularly clean to depend on an internal interface, but, unfortunately, __bool__
# must be defined on a class to be called during implicit conversion.
class _GuardedContextManager(_GeneratorContextManager):
    def __init__(self, keyword, func, args, kwds):
        self.keyword = keyword
        return super().__init__(func, args, kwds)

    def __bool__(self):
        raise SyntaxError("`if m.{kw}(...):` does not work; use `with m.{kw}(...)`"
                          .format(kw=self.keyword))


def _guardedcontextmanager(keyword):
    def decorator(func):
        @wraps(func)
        def helper(*args, **kwds):
            return _GuardedContextManager(keyword, func, args, kwds)
        return helper
    return decorator


class FSM:
    def __init__(self, state, encoding, decoding):
        self.state    = state
        self.encoding = encoding
        self.decoding = decoding

    def ongoing(self, name):
        if name not in self.encoding:
            self.encoding[name] = len(self.encoding)
        return Operator("==", [self.state, self.encoding[name]], src_loc_at=0)


class Module(_ModuleBuilderRoot, Elaboratable):
    @classmethod
    def __init_subclass__(cls):
        raise SyntaxError("Instead of inheriting from `Module`, inherit from `Elaboratable` "
                          "and return a `Module` from the `elaborate(self, platform)` method")

    def __init__(self):
        _ModuleBuilderRoot.__init__(self, self, depth=0)
        self.submodules    = _ModuleBuilderSubmodules(self)
        self.domains       = _ModuleBuilderDomainSet(self)

        self._statements   = Statement.cast([])
        self._ctrl_context = None
        self._ctrl_stack   = []

        self._driving      = SignalDict()
        self._named_submodules = {}
        self._anon_submodules  = []
        self._domains      = {}
        self._generated    = {}

    def _check_context(self, construct, context):
        if self._ctrl_context != context:
            if self._ctrl_context is None:
                raise SyntaxError("{} is not permitted outside of {}"
                                  .format(construct, context))
            else:
                if self._ctrl_context == "Switch":
                    secondary_context = "Case"
                if self._ctrl_context == "FSM":
                    secondary_context = "State"
                raise SyntaxError("{} is not permitted directly inside of {}; it is permitted "
                                  "inside of {} {}"
                                  .format(construct, self._ctrl_context,
                                          self._ctrl_context, secondary_context))

    def _get_ctrl(self, name):
        if self._ctrl_stack:
            top_name, top_data = self._ctrl_stack[-1]
            if top_name == name:
                return top_data

    def _flush_ctrl(self):
        while len(self._ctrl_stack) > self.domain._depth:
            self._pop_ctrl()

    def _set_ctrl(self, name, data):
        self._flush_ctrl()
        self._ctrl_stack.append((name, data))
        return data

    def _check_signed_cond(self, cond):
        cond = Value.cast(cond)
        if sys.version_info < (3, 12, 0) and cond.shape().signed:
            # TODO(py3.11): remove; ~True is a warning in 3.12+, finally!
            warnings.warn("Signed values in If/Elif conditions usually result from inverting "
                          "Python booleans with ~, which leads to unexpected results. "
                          "Replace `~flag` with `not flag`. (If this is a false positive, "
                          "silence this warning with `m.If(x)` → `m.If(x.bool())`.)",
                          SyntaxWarning, stacklevel=4)
        return cond

    @_guardedcontextmanager("If")
    def If(self, cond):
        self._check_context("If", context=None)
        cond = self._check_signed_cond(cond)
        src_loc = tracer.get_src_loc(src_loc_at=1)
        if_data = self._set_ctrl("If", {
            "depth":    self.domain._depth,
            "tests":    [],
            "bodies":   [],
            "src_loc":  src_loc,
            "src_locs": [],
        })
        try:
            _outer_case, self._statements = self._statements, []
            self.domain._depth += 1
            yield
            self._flush_ctrl()
            if_data["tests"].append(cond)
            if_data["bodies"].append(self._statements)
            if_data["src_locs"].append(src_loc)
        finally:
            self.domain._depth -= 1
            self._statements = _outer_case

    @_guardedcontextmanager("Elif")
    def Elif(self, cond):
        self._check_context("Elif", context=None)
        cond = self._check_signed_cond(cond)
        src_loc = tracer.get_src_loc(src_loc_at=1)
        if_data = self._get_ctrl("If")
        if if_data is None or if_data["depth"] != self.domain._depth:
            raise SyntaxError("Elif without preceding If")
        try:
            _outer_case, self._statements = self._statements, []
            self.domain._depth += 1
            yield
            self._flush_ctrl()
            if_data["tests"].append(cond)
            if_data["bodies"].append(self._statements)
            if_data["src_locs"].append(src_loc)
        finally:
            self.domain._depth -= 1
            self._statements = _outer_case

    @_guardedcontextmanager("Else")
    def Else(self):
        self._check_context("Else", context=None)
        src_loc = tracer.get_src_loc(src_loc_at=1)
        if_data = self._get_ctrl("If")
        if if_data is None or if_data["depth"] != self.domain._depth:
            raise SyntaxError("Else without preceding If/Elif")
        try:
            _outer_case, self._statements = self._statements, []
            self.domain._depth += 1
            yield
            self._flush_ctrl()
            if_data["bodies"].append(self._statements)
            if_data["src_locs"].append(src_loc)
        finally:
            self.domain._depth -= 1
            self._statements = _outer_case
        self._pop_ctrl()

    @contextmanager
    def Switch(self, test):
        self._check_context("Switch", context=None)
        switch_data = self._set_ctrl("Switch", {
            "test":    Value.cast(test),
            "cases":   OrderedDict(),
            "src_loc": tracer.get_src_loc(src_loc_at=1),
            "case_src_locs": {},
        })
        try:
            self._ctrl_context = "Switch"
            self.domain._depth += 1
            yield
        finally:
            self.domain._depth -= 1
            self._ctrl_context = None
        self._pop_ctrl()

    @contextmanager
    def Case(self, *patterns):
        self._check_context("Case", context="Switch")
        src_loc = tracer.get_src_loc(src_loc_at=1)
        switch_data = self._get_ctrl("Switch")
        new_patterns = ()
        # This code should accept exactly the same patterns as `v.matches(...)`.
        for pattern in patterns:
            if isinstance(pattern, str) and any(bit not in "01- \t" for bit in pattern):
                raise SyntaxError("Case pattern '{}' must consist of 0, 1, and - (don't care) "
                                  "bits, and may include whitespace"
                                  .format(pattern))
            if (isinstance(pattern, str) and
                    len("".join(pattern.split())) != len(switch_data["test"])):
                raise SyntaxError("Case pattern '{}' must have the same width as switch value "
                                  "(which is {})"
                                  .format(pattern, len(switch_data["test"])))
            if isinstance(pattern, str):
                new_patterns = (*new_patterns, pattern)
            else:
                try:
                    orig_pattern, pattern = pattern, Const.cast(pattern)
                except TypeError as e:
                    raise SyntaxError("Case pattern must be a string or a constant-castable "
                                      "expression, not {!r}"
                                      .format(pattern)) from e
                pattern_len = bits_for(pattern.value)
                if pattern_len > len(switch_data["test"]):
                    warnings.warn("Case pattern '{!r}' ({}'{:b}) is wider than switch value "
                                  "(which has width {}); comparison will never be true"
                                  .format(orig_pattern, pattern_len, pattern.value,
                                          len(switch_data["test"])),
                                  SyntaxWarning, stacklevel=3)
                    continue
                new_patterns = (*new_patterns, pattern.value)
        try:
            _outer_case, self._statements = self._statements, []
            self._ctrl_context = None
            yield
            self._flush_ctrl()
            # If none of the provided cases can possibly be true, omit this branch completely.
            # This needs to be differentiated from no cases being provided in the first place,
            # which means the branch will always match.
            if not (patterns and not new_patterns):
                switch_data["cases"][new_patterns] = self._statements
                switch_data["case_src_locs"][new_patterns] = src_loc
        finally:
            self._ctrl_context = "Switch"
            self._statements = _outer_case

    def Default(self):
        return self.Case()

    @contextmanager
    def FSM(self, reset=None, domain="sync", name="fsm"):
        self._check_context("FSM", context=None)
        if domain == "comb":
            raise ValueError("FSM may not be driven by the '{}' domain".format(domain))
        fsm_data = self._set_ctrl("FSM", {
            "name":     name,
            "signal":   Signal(name="{}_state".format(name), src_loc_at=2),
            "reset":    reset,
            "domain":   domain,
            "encoding": OrderedDict(),
            "decoding": OrderedDict(),
            "states":   OrderedDict(),
            "src_loc":  tracer.get_src_loc(src_loc_at=1),
            "state_src_locs": {},
        })
        self._generated[name] = fsm = \
            FSM(fsm_data["signal"], fsm_data["encoding"], fsm_data["decoding"])
        try:
            self._ctrl_context = "FSM"
            self.domain._depth += 1
            yield fsm
            for state_name in fsm_data["encoding"]:
                if state_name not in fsm_data["states"]:
                    raise NameError("FSM state '{}' is referenced but not defined"
                                    .format(state_name))
        finally:
            self.domain._depth -= 1
            self._ctrl_context = None
        self._pop_ctrl()

    @contextmanager
    def State(self, name):
        self._check_context("FSM State", context="FSM")
        src_loc = tracer.get_src_loc(src_loc_at=1)
        fsm_data = self._get_ctrl("FSM")
        if name in fsm_data["states"]:
            raise NameError("FSM state '{}' is already defined".format(name))
        if name not in fsm_data["encoding"]:
            fsm_data["encoding"][name] = len(fsm_data["encoding"])
        try:
            _outer_case, self._statements = self._statements, []
            self._ctrl_context = None
            yield
            self._flush_ctrl()
            fsm_data["states"][name] = self._statements
            fsm_data["state_src_locs"][name] = src_loc
        finally:
            self._ctrl_context = "FSM"
            self._statements = _outer_case

    @property
    def next(self):
        raise SyntaxError("Only assignment to `m.next` is permitted")

    @next.setter
    def next(self, name):
        if self._ctrl_context != "FSM":
            for level, (ctrl_name, ctrl_data) in enumerate(reversed(self._ctrl_stack)):
                if ctrl_name == "FSM":
                    if name not in ctrl_data["encoding"]:
                        ctrl_data["encoding"][name] = len(ctrl_data["encoding"])
                    self._add_statement(
                        assigns=[ctrl_data["signal"].eq(ctrl_data["encoding"][name])],
                        domain=ctrl_data["domain"],
                        depth=len(self._ctrl_stack))
                    return

        raise SyntaxError("`m.next = <...>` is only permitted inside an FSM state")

    def _pop_ctrl(self):
        name, data = self._ctrl_stack.pop()
        src_loc = data["src_loc"]

        if name == "If":
            if_tests, if_bodies = data["tests"], data["bodies"]
            if_src_locs = data["src_locs"]

            tests, cases = [], OrderedDict()
            for if_test, if_case in zip(if_tests + [None], if_bodies):
                if if_test is not None:
                    if len(if_test) != 1:
                        if_test = if_test.bool()
                    tests.append(if_test)

                if if_test is not None:
                    match = ("1" + "-" * (len(tests) - 1)).rjust(len(if_tests), "-")
                else:
                    match = None
                cases[match] = if_case

            self._statements.append(Switch(Cat(tests), cases,
                src_loc=src_loc, case_src_locs=dict(zip(cases, if_src_locs))))

        if name == "Switch":
            switch_test, switch_cases = data["test"], data["cases"]
            switch_case_src_locs = data["case_src_locs"]

            self._statements.append(Switch(switch_test, switch_cases,
                src_loc=src_loc, case_src_locs=switch_case_src_locs))

        if name == "FSM":
            fsm_signal, fsm_reset, fsm_encoding, fsm_decoding, fsm_states = \
                data["signal"], data["reset"], data["encoding"], data["decoding"], data["states"]
            fsm_state_src_locs = data["state_src_locs"]
            if not fsm_states:
                return
            fsm_signal.width = bits_for(len(fsm_encoding) - 1)
            if fsm_reset is None:
                fsm_signal.reset = fsm_encoding[next(iter(fsm_states))]
            else:
                fsm_signal.reset = fsm_encoding[fsm_reset]
            # The FSM is encoded such that the state with encoding 0 is always the reset state.
            fsm_decoding.update((n, s) for s, n in fsm_encoding.items())
            fsm_signal.decoder = lambda n: "{}/{}".format(fsm_decoding[n], n)
            self._statements.append(Switch(fsm_signal,
                OrderedDict((fsm_encoding[name], stmts) for name, stmts in fsm_states.items()),
                src_loc=src_loc, case_src_locs={fsm_encoding[name]: fsm_state_src_locs[name]
                                                for name in fsm_states}))

    def _add_statement(self, assigns, domain, depth, compat_mode=False):
        def domain_name(domain):
            if domain is None:
                return "comb"
            else:
                return domain

        while len(self._ctrl_stack) > self.domain._depth:
            self._pop_ctrl()

        for stmt in Statement.cast(assigns):
            if not compat_mode and not isinstance(stmt, (Assign, Assert, Assume, Cover)):
                raise SyntaxError(
                    "Only assignments and property checks may be appended to d.{}"
                    .format(domain_name(domain)))

            stmt._MustUse__used = True
            stmt = SampleDomainInjector(domain)(stmt)

            for signal in stmt._lhs_signals():
                if signal not in self._driving:
                    self._driving[signal] = domain
                elif self._driving[signal] != domain:
                    cd_curr = self._driving[signal]
                    raise SyntaxError(
                        "Driver-driver conflict: trying to drive {!r} from d.{}, but it is "
                        "already driven from d.{}"
                        .format(signal, domain_name(domain), domain_name(cd_curr)))

            self._statements.append(stmt)

    def _add_submodule(self, submodule, name=None):
        if not hasattr(submodule, "elaborate"):
            raise TypeError("Trying to add {!r}, which does not implement .elaborate(), as "
                            "a submodule".format(submodule))
        if name == None:
            self._anon_submodules.append(submodule)
        else:
            if name in self._named_submodules:
                raise NameError("Submodule named '{}' already exists".format(name))
            self._named_submodules[name] = submodule

    def _get_submodule(self, name):
        if name in self._named_submodules:
            return self._named_submodules[name]
        else:
            raise AttributeError("No submodule named '{}' exists".format(name))

    def _add_domain(self, cd):
        if cd.name in self._domains:
            raise NameError("Clock domain named '{}' already exists".format(cd.name))
        self._domains[cd.name] = cd

    def _flush(self):
        while self._ctrl_stack:
            self._pop_ctrl()

    def elaborate(self, platform):
        self._flush()

        fragment = Fragment()
        for name in self._named_submodules:
            fragment.add_subfragment(Fragment.get(self._named_submodules[name], platform), name)
        for submodule in self._anon_submodules:
            fragment.add_subfragment(Fragment.get(submodule, platform), None)
        statements = SampleDomainInjector("sync")(self._statements)
        fragment.add_statements(statements)
        for signal, domain in self._driving.items():
            fragment.add_driver(signal, domain)
        fragment.add_domains(self._domains.values())
        fragment.generated.update(self._generated)
        return fragment


# amaranth/hdl/ir.py

from abc import ABCMeta
from collections import defaultdict, OrderedDict
from functools import reduce
import warnings

from .._utils import *
from .._unused import *
from .ast import *
from .cd import *


__all__ = ["UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance"]


class UnusedElaboratable(UnusedMustUse):
    # The warning is initially silenced. If everything that has been constructed remains unused,
    # it means the application likely crashed (with an exception, or in another way that does not
    # call `sys.excepthook`), and it's not necessary to show any warnings.
    # Once elaboration starts, the warning is enabled.
    _MustUse__silence = True


class Elaboratable(MustUse, metaclass=ABCMeta):
    _MustUse__warning = UnusedElaboratable


class DriverConflict(UserWarning):
    pass


class Fragment:
    @staticmethod
    def get(obj, platform):
        code = None
        while True:
            if isinstance(obj, Fragment):
                return obj
            elif isinstance(obj, Elaboratable):
                code = obj.elaborate.__code__
                UnusedElaboratable._MustUse__silence = False
                obj._MustUse__used = True
                new_obj = obj.elaborate(platform)
            elif hasattr(obj, "elaborate"):
                warnings.warn(
                    message="Class {!r} is an elaboratable that does not explicitly inherit from "
                            "Elaboratable; doing so would improve diagnostics"
                            .format(type(obj)),
                    category=RuntimeWarning,
                    stacklevel=2)
                code = obj.elaborate.__code__
                new_obj = obj.elaborate(platform)
            else:
                raise AttributeError("Object {!r} cannot be elaborated".format(obj))
            if new_obj is obj:
                raise RecursionError("Object {!r} elaborates to itself".format(obj))
            if new_obj is None and code is not None:
                warnings.warn_explicit(
                    message=".elaborate() returned None; missing return statement?",
                    category=UserWarning,
                    filename=code.co_filename,
                    lineno=code.co_firstlineno)
            obj = new_obj

    def __init__(self):
        self.ports = SignalDict()
        self.drivers = OrderedDict()
        self.statements = []
        self.domains = OrderedDict()
        self.subfragments = []
        self.attrs = OrderedDict()
        self.generated = OrderedDict()
        self.flatten = False

    def add_ports(self, *ports, dir):
        assert dir in ("i", "o", "io")
        for port in flatten(ports):
            self.ports[port] = dir

    def iter_ports(self, dir=None):
        if dir is None:
            yield from self.ports
        else:
            for port, port_dir in self.ports.items():
                if port_dir == dir:
                    yield port

    def add_driver(self, signal, domain=None):
        if domain not in self.drivers:
            self.drivers[domain] = SignalSet()
        self.drivers[domain].add(signal)

    def iter_drivers(self):
        for domain, signals in self.drivers.items():
            for signal in signals:
                yield domain, signal

    def iter_comb(self):
        if None in self.drivers:
            yield from self.drivers[None]

    def iter_sync(self):
        for domain, signals in self.drivers.items():
            if domain is None:
                continue
            for signal in signals:
                yield domain, signal

    def iter_signals(self):
        signals = SignalSet()
        signals |= self.ports.keys()
        for domain, domain_signals in self.drivers.items():
            if domain is not None:
                cd = self.domains[domain]
                signals.add(cd.clk)
                if cd.rst is not None:
                    signals.add(cd.rst)
            signals |= domain_signals
        return signals

    def add_domains(self, *domains):
        for domain in flatten(domains):
            assert isinstance(domain, ClockDomain)
            assert domain.name not in self.domains
            self.domains[domain.name] = domain

    def iter_domains(self):
        yield from self.domains

    def add_statements(self, *stmts):
        for stmt in Statement.cast(stmts):
            stmt._MustUse__used = True
            self.statements.append(stmt)

    def add_subfragment(self, subfragment, name=None):
        assert isinstance(subfragment, Fragment)
        self.subfragments.append((subfragment, name))

    def find_subfragment(self, name_or_index):
        if isinstance(name_or_index, int):
            if name_or_index < len(self.subfragments):
                subfragment, name = self.subfragments[name_or_index]
                return subfragment
            raise NameError("No subfragment at index #{}".format(name_or_index))
        else:
            for subfragment, name in self.subfragments:
                if name == name_or_index:
                    return subfragment
            raise NameError("No subfragment with name '{}'".format(name_or_index))

    def find_generated(self, *path):
        if len(path) > 1:
            path_component, *path = path
            return self.find_subfragment(path_component).find_generated(*path)
        else:
            item, = path
            return self.generated[item]

    def elaborate(self, platform):
        return self

    def _merge_subfragment(self, subfragment):
        # Merge subfragment's everything except clock domains into this fragment.
        # Flattening is done after clock domain propagation, so we can assume the domains
        # are already the same in every involved fragment in the first place.
        self.ports.update(subfragment.ports)
        for domain, signal in subfragment.iter_drivers():
            self.add_driver(signal, domain)
        self.statements += subfragment.statements
        self.subfragments += subfragment.subfragments

        # Remove the merged subfragment.
        found = False
        for i, (check_subfrag, check_name) in enumerate(self.subfragments): # :nobr:
            if subfragment == check_subfrag:
                del self.subfragments[i]
                found = True
                break
        assert found

    def _resolve_hierarchy_conflicts(self, hierarchy=("top",), mode="warn"):
        assert mode in ("silent", "warn", "error")

        driver_subfrags = SignalDict()
        memory_subfrags = OrderedDict()
        def add_subfrag(registry, entity, entry):
            # Because of missing domain insertion, at the point when this code runs, we have
            # a mixture of bound and unbound {Clock,Reset}Signals. Map the bound ones to
            # the actual signals (because the signal itself can be driven as well); but leave
            # the unbound ones as it is, because there's no concrete signal for it yet anyway.
            if isinstance(entity, ClockSignal) and entity.domain in self.domains:
                entity = self.domains[entity.domain].clk
            elif isinstance(entity, ResetSignal) and entity.domain in self.domains:
                entity = self.domains[entity.domain].rst

            if entity not in registry:
                registry[entity] = set()
            registry[entity].add(entry)

        # For each signal driven by this fragment and/or its subfragments, determine which
        # subfragments also drive it.
        for domain, signal in self.iter_drivers():
            add_subfrag(driver_subfrags, signal, (None, hierarchy))

        flatten_subfrags = set()
        for i, (subfrag, name) in enumerate(self.subfragments):
            if name is None:
                name = "<unnamed #{}>".format(i)
            subfrag_hierarchy = hierarchy + (name,)

            if subfrag.flatten:
                # Always flatten subfragments that explicitly request it.
                flatten_subfrags.add((subfrag, subfrag_hierarchy))

            if isinstance(subfrag, Instance):
                # For memories (which are subfragments, but semantically a part of superfragment),
                # record that this fragment is driving it.
                if subfrag.type in ("$memrd", "$memwr"):
                    memory = subfrag.parameters["MEMID"]
                    add_subfrag(memory_subfrags, memory, (None, hierarchy))

                # Never flatten instances.
                continue

            # First, recurse into subfragments and let them detect driver conflicts as well.
            subfrag_drivers, subfrag_memories = \
                subfrag._resolve_hierarchy_conflicts(subfrag_hierarchy, mode)

            # Second, classify subfragments by signals they drive and memories they use.
            for signal in subfrag_drivers:
                add_subfrag(driver_subfrags, signal, (subfrag, subfrag_hierarchy))
            for memory in subfrag_memories:
                add_subfrag(memory_subfrags, memory, (subfrag, subfrag_hierarchy))

        # Find out the set of subfragments that needs to be flattened into this fragment
        # to resolve driver-driver conflicts.
        def flatten_subfrags_if_needed(subfrags):
            if len(subfrags) == 1:
                return []
            flatten_subfrags.update((f, h) for f, h in subfrags if f is not None)
            return list(sorted(".".join(h) for f, h in subfrags))

        for signal, subfrags in driver_subfrags.items():
            subfrag_names = flatten_subfrags_if_needed(subfrags)
            if not subfrag_names:
                continue

            # While we're at it, show a message.
            message = ("Signal '{}' is driven from multiple fragments: {}"
                       .format(signal, ", ".join(subfrag_names)))
            if mode == "error":
                raise DriverConflict(message)
            elif mode == "warn":
                message += "; hierarchy will be flattened"
                warnings.warn_explicit(message, DriverConflict, *signal.src_loc)

        for memory, subfrags in memory_subfrags.items():
            subfrag_names = flatten_subfrags_if_needed(subfrags)
            if not subfrag_names:
                continue

            # While we're at it, show a message.
            message = ("Memory '{}' is accessed from multiple fragments: {}"
                       .format(memory.name, ", ".join(subfrag_names)))
            if mode == "error":
                raise DriverConflict(message)
            elif mode == "warn":
                message += "; hierarchy will be flattened"
                warnings.warn_explicit(message, DriverConflict, *memory.src_loc)

        # Flatten hierarchy.
        for subfrag, subfrag_hierarchy in sorted(flatten_subfrags, key=lambda x: x[1]):
            self._merge_subfragment(subfrag)

        # If we flattened anything, we might be in a situation where we have a driver conflict
        # again, e.g. if we had a tree of fragments like A --- B --- C where only fragments
        # A and C were driving a signal S. In that case, since B is not driving S itself,
        # processing B will not result in any flattening, but since B is transitively driving S,
        # processing A will flatten B into it. Afterwards, we have a tree like AB --- C, which
        # has another conflict.
        if any(flatten_subfrags):
            # Try flattening again.
            return self._resolve_hierarchy_conflicts(hierarchy, mode)

        # Nothing was flattened, we're done!
        return (SignalSet(driver_subfrags.keys()),
                set(memory_subfrags.keys()))

    def _propagate_domains_up(self, hierarchy=("top",)):
        from .xfrm import DomainRenamer

        domain_subfrags = defaultdict(lambda: set())

        # For each domain defined by a subfragment, determine which subfragments define it.
        for i, (subfrag, name) in enumerate(self.subfragments):
            # First, recurse into subfragments and let them propagate domains up as well.
            hier_name = name
            if hier_name is None:
                hier_name = "<unnamed #{}>".format(i)
            subfrag._propagate_domains_up(hierarchy + (hier_name,))

            # Second, classify subfragments by domains they define.
            for domain_name, domain in subfrag.domains.items():
                if domain.local:
                    continue
                domain_subfrags[domain_name].add((subfrag, name, i))

        # For each domain defined by more than one subfragment, rename the domain in each
        # of the subfragments such that they no longer conflict.
        for domain_name, subfrags in domain_subfrags.items():
            if len(subfrags) == 1:
                continue

            names = [n for f, n, i in subfrags]
            if not all(names):
                names = sorted("<unnamed #{}>".format(i) if n is None else "'{}'".format(n)
                               for f, n, i in subfrags)
                raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}'; "
                                  "it is necessary to either rename subfragment domains "
                                  "explicitly, or give names to subfragments"
                                  .format(domain_name, ", ".join(names), ".".join(hierarchy)))

            if len(names) != len(set(names)):
                names = sorted("#{}".format(i) for f, n, i in subfrags)
                raise DomainError("Domain '{}' is defined by subfragments {} of fragment '{}', "
                                  "some of which have identical names; it is necessary to either "
                                  "rename subfragment domains explicitly, or give distinct names "
                                  "to subfragments"
                                  .format(domain_name, ", ".join(names), ".".join(hierarchy)))

            for subfrag, name, i in subfrags:
                domain_name_map = {domain_name: "{}_{}".format(name, domain_name)}
                self.subfragments[i] = (DomainRenamer(domain_name_map)(subfrag), name)

        # Finally, collect the (now unique) subfragment domains, and merge them into our domains.
        for subfrag, name in self.subfragments:
            for domain_name, domain in subfrag.domains.items():
                if domain.local:
                    continue
                self.add_domains(domain)

    def _propagate_domains_down(self):
        # For each domain defined in this fragment, ensure it also exists in all subfragments.
        for subfrag, name in self.subfragments:
            for domain in self.iter_domains():
                if domain in subfrag.domains:
                    assert self.domains[domain] is subfrag.domains[domain]
                else:
                    subfrag.add_domains(self.domains[domain])

            subfrag._propagate_domains_down()

    def _create_missing_domains(self, missing_domain, *, platform=None):
        from .xfrm import DomainCollector

        collector = DomainCollector()
        collector(self)

        new_domains = []
        for domain_name in collector.used_domains - collector.defined_domains:
            if domain_name is None:
                continue
            value = missing_domain(domain_name)
            if value is None:
                raise DomainError("Domain '{}' is used but not defined".format(domain_name))
            if type(value) is ClockDomain:
                self.add_domains(value)
                # And expose ports on the newly added clock domain, since it is added directly
                # and there was no chance to add any logic driving it.
                new_domains.append(value)
            else:
                new_fragment = Fragment.get(value, platform=platform)
                if domain_name not in new_fragment.domains:
                    defined = new_fragment.domains.keys()
                    raise DomainError(
                        "Fragment returned by missing domain callback does not define "
                        "requested domain '{}' (defines {})."
                        .format(domain_name, ", ".join("'{}'".format(n) for n in defined)))
                self.add_subfragment(new_fragment, "cd_{}".format(domain_name))
                self.add_domains(new_fragment.domains.values())
        return new_domains

    def _propagate_domains(self, missing_domain, *, platform=None):
        self._propagate_domains_up()
        self._propagate_domains_down()
        self._resolve_hierarchy_conflicts()
        new_domains = self._create_missing_domains(missing_domain, platform=platform)
        self._propagate_domains_down()
        return new_domains

    def _prepare_use_def_graph(self, parent, level, uses, defs, ios, top):
        def add_uses(*sigs, self=self):
            for sig in flatten(sigs):
                if sig not in uses:
                    uses[sig] = set()
                uses[sig].add(self)

        def add_defs(*sigs):
            for sig in flatten(sigs):
                if sig not in defs:
                    defs[sig] = self
                else:
                    assert defs[sig] is self

        def add_io(*sigs):
            for sig in flatten(sigs):
                if sig not in ios:
                    ios[sig] = self
                else:
                    assert ios[sig] is self

        # Collect all signals we're driving (on LHS of statements), and signals we're using
        # (on RHS of statements, or in clock domains).
        for stmt in self.statements:
            add_uses(stmt._rhs_signals())
            add_defs(stmt._lhs_signals())

        for domain, _ in self.iter_sync():
            cd = self.domains[domain]
            add_uses(cd.clk)
            if cd.rst is not None:
                add_uses(cd.rst)

        # Repeat for subfragments.
        for subfrag, name in self.subfragments:
            if isinstance(subfrag, Instance):
                for port_name, (value, dir) in subfrag.named_ports.items():
                    if dir == "i":
                        # Prioritize defs over uses.
                        rhs_without_outputs = value._rhs_signals() - subfrag.iter_ports(dir="o")
                        subfrag.add_ports(rhs_without_outputs, dir=dir)
                        add_uses(value._rhs_signals())
                    if dir == "o":
                        subfrag.add_ports(value._lhs_signals(), dir=dir)
                        add_defs(value._lhs_signals())
                    if dir == "io":
                        subfrag.add_ports(value._lhs_signals(), dir=dir)
                        add_io(value._lhs_signals())
            else:
                parent[subfrag] = self
                level [subfrag] = level[self] + 1

                subfrag._prepare_use_def_graph(parent, level, uses, defs, ios, top)

    def _propagate_ports(self, ports, all_undef_as_ports):
        # Take this fragment graph:
        #
        #    __ B (def: q, use: p r)
        #   /
        #  A (def: p, use: q r)
        #   \
        #    \_ C (def: r, use: p q)
        #
        # We need to consider three cases.
        #   1. Signal p requires an input port in B;
        #   2. Signal r requires an output port in C;
        #   3. Signal r requires an output port in C and an input port in B.
        #
        # Adding these ports can be in general done in three steps for each signal:
        #   1. Find the least common ancestor of all uses and defs.
        #   2. Going upwards from the single def, add output ports.
        #   3. Going upwards from all uses, add input ports.

        parent = {self: None}
        level  = {self: 0}
        uses   = SignalDict()
        defs   = SignalDict()
        ios    = SignalDict()
        self._prepare_use_def_graph(parent, level, uses, defs, ios, self)

        ports = SignalSet(ports)
        if all_undef_as_ports:
            for sig in uses:
                if sig in defs:
                    continue
                ports.add(sig)
        for sig in ports:
            if sig not in uses:
                uses[sig] = set()
            uses[sig].add(self)

        @memoize
        def lca_of(fragu, fragv):
            # Normalize fragu to be deeper than fragv.
            if level[fragu] < level[fragv]:
                fragu, fragv = fragv, fragu
            # Find ancestor of fragu on the same level as fragv.
            for _ in range(level[fragu] - level[fragv]):
                fragu = parent[fragu]
            # If fragv was the ancestor of fragv, we're done.
            if fragu == fragv:
                return fragu
            # Otherwise, they are at the same level but in different branches. Step both fragu
            # and fragv until we find the common ancestor.
            while parent[fragu] != parent[fragv]:
                fragu = parent[fragu]
                fragv = parent[fragv]
            return parent[fragu]

        for sig in uses:
            if sig in defs:
                lca  = reduce(lca_of, uses[sig], defs[sig])
            else:
                lca  = reduce(lca_of, uses[sig])

            for frag in uses[sig]:
                if sig in defs and frag is defs[sig]:
                    continue
                while frag != lca:
                    frag.add_ports(sig, dir="i")
                    frag = parent[frag]

            if sig in defs:
                frag = defs[sig]
                while frag != lca:
                    frag.add_ports(sig, dir="o")
                    frag = parent[frag]

        for sig in ios:
            frag = ios[sig]
            while frag is not None:
                frag.add_ports(sig, dir="io")
                frag = parent[frag]

        for sig in ports:
            if sig in ios:
                continue
            if sig in defs:
                self.add_ports(sig, dir="o")
            else:
                self.add_ports(sig, dir="i")

    def prepare(self, ports=None, missing_domain=lambda name: ClockDomain(name)):
        from .xfrm import SampleLowerer, DomainLowerer

        fragment = SampleLowerer()(self)
        new_domains = fragment._propagate_domains(missing_domain)
        fragment = DomainLowerer()(fragment)
        if ports is None:
            fragment._propagate_ports(ports=(), all_undef_as_ports=True)
        else:
            if not isinstance(ports, tuple) and not isinstance(ports, list):
                msg = "`ports` must be either a list or a tuple, not {!r}"\
                        .format(ports)
                if isinstance(ports, Value):
                    msg += " (did you mean `ports=(<signal>,)`, rather than `ports=<signal>`?)"
                raise TypeError(msg)
            mapped_ports = []
            # Lower late bound signals like ClockSignal() to ports.
            port_lowerer = DomainLowerer(fragment.domains)
            for port in ports:
                if not isinstance(port, (Signal, ClockSignal, ResetSignal)):
                    raise TypeError("Only signals may be added as ports, not {!r}"
                                    .format(port))
                mapped_ports.append(port_lowerer.on_value(port))
            # Add ports for all newly created missing clock domains, since not doing so defeats
            # the purpose of domain auto-creation. (It's possible to refer to these ports before
            # the domain actually exists through late binding, but it's inconvenient.)
            for cd in new_domains:
                mapped_ports.append(cd.clk)
                if cd.rst is not None:
                    mapped_ports.append(cd.rst)
            fragment._propagate_ports(ports=mapped_ports, all_undef_as_ports=False)
        return fragment


class Instance(Fragment):
    def __init__(self, type, *args, **kwargs):
        super().__init__()

        self.type        = type
        self.parameters  = OrderedDict()
        self.named_ports = OrderedDict()

        for (kind, name, value) in args:
            if kind == "a":
                self.attrs[name] = value
            elif kind == "p":
                self.parameters[name] = value
            elif kind in ("i", "o", "io"):
                self.named_ports[name] = (Value.cast(value), kind)
            else:
                raise NameError("Instance argument {!r} should be a tuple (kind, name, value) "
                                "where kind is one of \"a\", \"p\", \"i\", \"o\", or \"io\""
                                .format((kind, name, value)))

        for kw, arg in kwargs.items():
            if kw.startswith("a_"):
                self.attrs[kw[2:]] = arg
            elif kw.startswith("p_"):
                self.parameters[kw[2:]] = arg
            elif kw.startswith("i_"):
                self.named_ports[kw[2:]] = (Value.cast(arg), "i")
            elif kw.startswith("o_"):
                self.named_ports[kw[2:]] = (Value.cast(arg), "o")
            elif kw.startswith("io_"):
                self.named_ports[kw[3:]] = (Value.cast(arg), "io")
            else:
                raise NameError("Instance keyword argument {}={!r} does not start with one of "
                                "\"a_\", \"p_\", \"i_\", \"o_\", or \"io_\""
                                .format(kw, arg))


# amaranth/hdl/mem.py

import operator
from collections import OrderedDict

from .. import tracer
from .ast import *
from .ir import Elaboratable, Instance


__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"]


class Memory:
    """A word addressable storage.

    Parameters
    ----------
    width : int
        Access granularity. Each storage element of this memory is ``width`` bits in size.
    depth : int
        Word count. This memory contains ``depth`` storage elements.
    init : list of int
        Initial values. At power on, each storage element in this memory is initialized to
        the corresponding element of ``init``, if any, or to zero otherwise.
        Uninitialized memories are not currently supported.
    name : str
        Name hint for this memory. If ``None`` (default) the name is inferred from the variable
        name this ``Signal`` is assigned to.
    attrs : dict
        Dictionary of synthesis attributes.

    Attributes
    ----------
    width : int
    depth : int
    init : list of int
    attrs : dict
    """
    def __init__(self, *, width, depth, init=None, name=None, attrs=None, simulate=True):
        if not isinstance(width, int) or width < 0:
            raise TypeError("Memory width must be a non-negative integer, not {!r}"
                            .format(width))
        if not isinstance(depth, int) or depth < 0:
            raise TypeError("Memory depth must be a non-negative integer, not {!r}"
                            .format(depth))

        self.name    = name or tracer.get_var_name(depth=2, default="$memory")
        self.src_loc = tracer.get_src_loc()

        self.width = width
        self.depth = depth
        self.attrs = OrderedDict(() if attrs is None else attrs)

        # Array of signals for simulation.
        self._array = Array()
        if simulate:
            for addr in range(self.depth):
                self._array.append(Signal(self.width, name="{}({})"
                                          .format(name or "memory", addr)))

        self.init = init

    @property
    def init(self):
        return self._init

    @init.setter
    def init(self, new_init):
        self._init = [] if new_init is None else list(new_init)
        if len(self.init) > self.depth:
            raise ValueError("Memory initialization value count exceed memory depth ({} > {})"
                             .format(len(self.init), self.depth))

        try:
            for addr in range(len(self._array)):
                if addr < len(self._init):
                    self._array[addr].reset = operator.index(self._init[addr])
                else:
                    self._array[addr].reset = 0
        except TypeError as e:
            raise TypeError("Memory initialization value at address {:x}: {}"
                            .format(addr, e)) from None

    def read_port(self, *, src_loc_at=0, **kwargs):
        """Get a read port.

        See :class:`ReadPort` for details.

        Arguments
        ---------
        domain : str
        transparent : bool

        Returns
        -------
        An instance of :class:`ReadPort` associated with this memory.
        """
        return ReadPort(self, src_loc_at=1 + src_loc_at, **kwargs)

    def write_port(self, *, src_loc_at=0, **kwargs):
        """Get a write port.

        See :class:`WritePort` for details.

        Arguments
        ---------
        domain : str
        granularity : int

        Returns
        -------
        An instance of :class:`WritePort` associated with this memory.
        """
        return WritePort(self, src_loc_at=1 + src_loc_at, **kwargs)

    def __getitem__(self, index):
        """Simulation only."""
        return self._array[index]


class ReadPort(Elaboratable):
    """A memory read port.

    Parameters
    ----------
    memory : :class:`Memory`
        Memory associated with the port.
    domain : str
        Clock domain. Defaults to ``"sync"``. If set to ``"comb"``, the port is asynchronous.
        Otherwise, the read data becomes available on the next clock cycle.
    transparent : bool
        Port transparency. If set (default), a read at an address that is also being written to in
        the same clock cycle will output the new value. Otherwise, the old value will be output
        first. This behavior only applies to ports in the same domain.

    Attributes
    ----------
    memory : :class:`Memory`
    domain : str
    transparent : bool
    addr : Signal(range(memory.depth)), in
        Read address.
    data : Signal(memory.width), out
        Read data.
    en : Signal or Const, in
        Read enable. If asserted, ``data`` is updated with the word stored at ``addr``. Note that
        transparent ports cannot assign ``en`` (which is hardwired to 1 instead), as doing so is
        currently not supported by Yosys.

    Exceptions
    ----------
    Raises :exn:`ValueError` if the read port is simultaneously asynchronous and non-transparent.
    """
    def __init__(self, memory, *, domain="sync", transparent=True, src_loc_at=0):
        if domain == "comb" and not transparent:
            raise ValueError("Read port cannot be simultaneously asynchronous and non-transparent")

        self.memory      = memory
        self.domain      = domain
        self.transparent = transparent

        self.addr = Signal(range(memory.depth),
                           name="{}_r_addr".format(memory.name), src_loc_at=1 + src_loc_at)
        self.data = Signal(memory.width,
                           name="{}_r_data".format(memory.name), src_loc_at=1 + src_loc_at)
        if self.domain != "comb" and not transparent:
            self.en = Signal(name="{}_r_en".format(memory.name), reset=1,
                             src_loc_at=1 + src_loc_at)
        else:
            self.en = Const(1)

    def elaborate(self, platform):
        f = Instance("$memrd",
            p_MEMID=self.memory,
            p_ABITS=self.addr.width,
            p_WIDTH=self.data.width,
            p_CLK_ENABLE=self.domain != "comb",
            p_CLK_POLARITY=1,
            p_TRANSPARENT=self.transparent,
            i_CLK=ClockSignal(self.domain) if self.domain != "comb" else Const(0),
            i_EN=self.en,
            i_ADDR=self.addr,
            o_DATA=self.data,
        )
        if self.domain == "comb":
            # Asynchronous port
            f.add_statements(self.data.eq(self.memory._array[self.addr]))
            f.add_driver(self.data)
        elif not self.transparent:
            # Synchronous, read-before-write port
            f.add_statements(
                Switch(self.en, {
                    1: self.data.eq(self.memory._array[self.addr])
                })
            )
            f.add_driver(self.data, self.domain)
        else:
            # Synchronous, write-through port
            # This model is a bit unconventional. We model transparent ports as asynchronous ports
            # that are latched when the clock is high. This isn't exactly correct, but it is very
            # close to the correct behavior of a transparent port, and the difference should only
            # be observable in pathological cases of clock gating. A register is injected to
            # the address input to achieve the correct address-to-data latency. Also, the reset
            # value of the data output is forcibly set to the 0th initial value, if any--note that
            # many FPGAs do not guarantee this behavior!
            if len(self.memory.init) > 0:
                self.data.reset = operator.index(self.memory.init[0])
            latch_addr = Signal.like(self.addr)
            f.add_statements(
                latch_addr.eq(self.addr),
                Switch(ClockSignal(self.domain), {
                    0: self.data.eq(self.data),
                    1: self.data.eq(self.memory._array[latch_addr]),
                }),
            )
            f.add_driver(latch_addr, self.domain)
            f.add_driver(self.data)
        return f


class WritePort(Elaboratable):
    """A memory write port.

    Parameters
    ----------
    memory : :class:`Memory`
        Memory associated with the port.
    domain : str
        Clock domain. Defaults to ``"sync"``. Writes have a latency of 1 clock cycle.
    granularity : int
        Port granularity. Defaults to ``memory.width``. Write data is split evenly in
        ``memory.width // granularity`` chunks, which can be updated independently.

    Attributes
    ----------
    memory : :class:`Memory`
    domain : str
    granularity : int
    addr : Signal(range(memory.depth)), in
        Write address.
    data : Signal(memory.width), in
        Write data.
    en : Signal(memory.width // granularity), in
        Write enable. Each bit selects a non-overlapping chunk of ``granularity`` bits on the
        ``data`` signal, which is written to memory at ``addr``. Unselected chunks are ignored.

    Exceptions
    ----------
    Raises :exn:`ValueError` if the write port granularity is greater than memory width, or does not
    divide memory width evenly.
    """
    def __init__(self, memory, *, domain="sync", granularity=None, src_loc_at=0):
        if granularity is None:
            granularity = memory.width
        if not isinstance(granularity, int) or granularity < 0:
            raise TypeError("Write port granularity must be a non-negative integer, not {!r}"
                            .format(granularity))
        if granularity > memory.width:
            raise ValueError("Write port granularity must not be greater than memory width "
                             "({} > {})"
                             .format(granularity, memory.width))
        if memory.width // granularity * granularity != memory.width:
            raise ValueError("Write port granularity must divide memory width evenly")

        self.memory       = memory
        self.domain       = domain
        self.granularity  = granularity

        self.addr = Signal(range(memory.depth),
                           name="{}_w_addr".format(memory.name), src_loc_at=1 + src_loc_at)
        self.data = Signal(memory.width,
                           name="{}_w_data".format(memory.name), src_loc_at=1 + src_loc_at)
        self.en   = Signal(memory.width // granularity,
                           name="{}_w_en".format(memory.name), src_loc_at=1 + src_loc_at)

    def elaborate(self, platform):
        f = Instance("$memwr",
            p_MEMID=self.memory,
            p_ABITS=self.addr.width,
            p_WIDTH=self.data.width,
            p_CLK_ENABLE=1,
            p_CLK_POLARITY=1,
            p_PRIORITY=0,
            i_CLK=ClockSignal(self.domain),
            i_EN=Cat(en_bit.replicate(self.granularity) for en_bit in self.en),
            i_ADDR=self.addr,
            i_DATA=self.data,
        )
        if len(self.en) > 1:
            for index, en_bit in enumerate(self.en):
                offset = index * self.granularity
                bits   = slice(offset, offset + self.granularity)
                write_data = self.memory._array[self.addr][bits].eq(self.data[bits])
                f.add_statements(Switch(en_bit, { 1: write_data }))
        else:
            write_data = self.memory._array[self.addr].eq(self.data)
            f.add_statements(Switch(self.en, { 1: write_data }))
        for signal in self.memory._array:
            f.add_driver(signal, self.domain)
        return f


class DummyPort:
    """Dummy memory port.

    This port can be used in place of either a read or a write port for testing and verification.
    It does not include any read/write port specific attributes, i.e. none besides ``"domain"``;
    any such attributes may be set manually.
    """
    def __init__(self, *, data_width, addr_width, domain="sync", name=None, granularity=None):
        self.domain = domain

        if granularity is None:
            granularity = data_width
        if name is None:
            name = tracer.get_var_name(depth=2, default="dummy")

        self.addr = Signal(addr_width,
                           name="{}_addr".format(name), src_loc_at=1)
        self.data = Signal(data_width,
                           name="{}_data".format(name), src_loc_at=1)
        self.en   = Signal(data_width // granularity,
                           name="{}_en".format(name), src_loc_at=1)


# amaranth/hdl/rec.py

from enum import Enum
from collections import OrderedDict
from functools import reduce, wraps

from .. import tracer
from .._utils import union
from .ast import *


__all__ = ["Direction", "DIR_NONE", "DIR_FANOUT", "DIR_FANIN", "Layout", "Record"]


Direction = Enum('Direction', ('NONE', 'FANOUT', 'FANIN'))

DIR_NONE   = Direction.NONE
DIR_FANOUT = Direction.FANOUT
DIR_FANIN  = Direction.FANIN


class Layout:
    @staticmethod
    def cast(obj, *, src_loc_at=0):
        if isinstance(obj, Layout):
            return obj
        return Layout(obj, src_loc_at=1 + src_loc_at)

    def __init__(self, fields, *, src_loc_at=0):
        self.fields = OrderedDict()
        for field in fields:
            if not isinstance(field, tuple) or len(field) not in (2, 3):
                raise TypeError("Field {!r} has invalid layout: should be either "
                                "(name, shape) or (name, shape, direction)"
                                .format(field))
            if len(field) == 2:
                name, shape = field
                direction = DIR_NONE
                if isinstance(shape, list):
                    shape = Layout.cast(shape)
            else:
                name, shape, direction = field
                if not isinstance(direction, Direction):
                    raise TypeError("Field {!r} has invalid direction: should be a Direction "
                                    "instance like DIR_FANIN"
                                    .format(field))
            if not isinstance(name, str):
                raise TypeError("Field {!r} has invalid name: should be a string"
                                .format(field))
            if not isinstance(shape, Layout):
                try:
                    # Check provided shape by calling Shape.cast and checking for exception
                    Shape.cast(shape, src_loc_at=1 + src_loc_at)
                except Exception:
                    raise TypeError("Field {!r} has invalid shape: should be castable to Shape "
                                    "or a list of fields of a nested record"
                                    .format(field))
            if name in self.fields:
                raise NameError("Field {!r} has a name that is already present in the layout"
                                .format(field))
            self.fields[name] = (shape, direction)

    def __getitem__(self, item):
        if isinstance(item, tuple):
            return Layout([
                (name, shape, dir)
                for (name, (shape, dir)) in self.fields.items()
                if name in item
            ])

        return self.fields[item]

    def __iter__(self):
        for name, (shape, dir) in self.fields.items():
            yield (name, shape, dir)

    def __eq__(self, other):
        return self.fields == other.fields

    def __repr__(self):
        field_reprs = []
        for name, shape, dir in self:
            if dir == DIR_NONE:
                field_reprs.append("({!r}, {!r})".format(name, shape))
            else:
                field_reprs.append("({!r}, {!r}, Direction.{})".format(name, shape, dir.name))
        return "Layout([{}])".format(", ".join(field_reprs))


class Record(ValueCastable):
    @staticmethod
    def like(other, *, name=None, name_suffix=None, src_loc_at=0):
        if name is not None:
            new_name = str(name)
        elif name_suffix is not None:
            new_name = other.name + str(name_suffix)
        else:
            new_name = tracer.get_var_name(depth=2 + src_loc_at, default=None)

        def concat(a, b):
            if a is None:
                return b
            return "{}__{}".format(a, b)

        fields = {}
        for field_name in other.fields:
            field = other[field_name]
            if isinstance(field, Record):
                fields[field_name] = Record.like(field, name=concat(new_name, field_name),
                                                 src_loc_at=1 + src_loc_at)
            else:
                fields[field_name] = Signal.like(field, name=concat(new_name, field_name),
                                                 src_loc_at=1 + src_loc_at)

        return Record(other.layout, name=new_name, fields=fields, src_loc_at=1)

    def __init__(self, layout, *, name=None, fields=None, src_loc_at=0):
        if name is None:
            name = tracer.get_var_name(depth=2 + src_loc_at, default=None)

        self.name    = name
        self.src_loc = tracer.get_src_loc(src_loc_at)

        def concat(a, b):
            if a is None:
                return b
            return "{}__{}".format(a, b)

        self.layout = Layout.cast(layout, src_loc_at=1 + src_loc_at)
        self.fields = OrderedDict()
        for field_name, field_shape, field_dir in self.layout:
            if fields is not None and field_name in fields:
                field = fields[field_name]
                if isinstance(field_shape, Layout):
                    assert isinstance(field, Record) and field_shape == field.layout
                else:
                    assert isinstance(field, Signal) and Shape.cast(field_shape) == field.shape()
                self.fields[field_name] = field
            else:
                if isinstance(field_shape, Layout):
                    self.fields[field_name] = Record(field_shape, name=concat(name, field_name),
                                                     src_loc_at=1 + src_loc_at)
                else:
                    self.fields[field_name] = Signal(field_shape, name=concat(name, field_name),
                                                     src_loc_at=1 + src_loc_at)

    def __getattr__(self, name):
        return self[name]

    def __getitem__(self, item):
        if isinstance(item, str):
            try:
                return self.fields[item]
            except KeyError:
                if self.name is None:
                    reference = "Unnamed record"
                else:
                    reference = "Record '{}'".format(self.name)
                raise AttributeError("{} does not have a field '{}'. Did you mean one of: {}?"
                                     .format(reference, item, ", ".join(self.fields))) from None
        elif isinstance(item, tuple):
            return Record(self.layout[item], fields={
                field_name: field_value
                for field_name, field_value in self.fields.items()
                if field_name in item
            })
        else:
            try:
                return Value.__getitem__(self, item)
            except KeyError:
                if self.name is None:
                    reference = "Unnamed record"
                else:
                    reference = "Record '{}'".format(self.name)
                raise AttributeError("{} does not have a field '{}'. Did you mean one of: {}?"
                                     .format(reference, item, ", ".join(self.fields))) from None

    @ValueCastable.lowermethod
    def as_value(self):
        return Cat(self.fields.values())

    def __len__(self):
        return len(self.as_value())

    def _lhs_signals(self):
        return union((f._lhs_signals() for f in self.fields.values()), start=SignalSet())

    def _rhs_signals(self):
        return union((f._rhs_signals() for f in self.fields.values()), start=SignalSet())

    def __repr__(self):
        fields = []
        for field_name, field in self.fields.items():
            if isinstance(field, Signal):
                fields.append(field_name)
            else:
                fields.append(repr(field))
        name = self.name
        if name is None:
            name = "<unnamed>"
        return "(rec {} {})".format(name, " ".join(fields))

    def shape(self):
        return self.as_value().shape()

    def connect(self, *subordinates, include=None, exclude=None):
        def rec_name(record):
            if record.name is None:
                return "unnamed record"
            else:
                return "record '{}'".format(record.name)

        for field in include or {}:
            if field not in self.fields:
                raise AttributeError("Cannot include field '{}' because it is not present in {}"
                                     .format(field, rec_name(self)))
        for field in exclude or {}:
            if field not in self.fields:
                raise AttributeError("Cannot exclude field '{}' because it is not present in {}"
                                     .format(field, rec_name(self)))

        stmts = []
        for field in self.fields:
            if include is not None and field not in include:
                continue
            if exclude is not None and field in exclude:
                continue

            shape, direction = self.layout[field]
            if not isinstance(shape, Layout) and direction == DIR_NONE:
                raise TypeError("Cannot connect field '{}' of {} because it does not have "
                                "a direction"
                                .format(field, rec_name(self)))

            item = self.fields[field]
            subord_items = []
            for subord in subordinates:
                if field not in subord.fields:
                    raise AttributeError("Cannot connect field '{}' of {} to subordinate {} "
                                         "because the subordinate record does not have this field"
                                         .format(field, rec_name(self), rec_name(subord)))
                subord_items.append(subord.fields[field])

            if isinstance(shape, Layout):
                sub_include = include[field] if include and field in include else None
                sub_exclude = exclude[field] if exclude and field in exclude else None
                stmts += item.connect(*subord_items, include=sub_include, exclude=sub_exclude)
            else:
                if direction == DIR_FANOUT:
                    stmts += [sub_item.eq(item) for sub_item in subord_items]
                if direction == DIR_FANIN:
                    stmts += [item.eq(reduce(lambda a, b: a | b, subord_items))]

        return stmts

def _valueproxy(name):
    value_func = getattr(Value, name)
    @wraps(value_func)
    def _wrapper(self, *args, **kwargs):
        return value_func(Value.cast(self), *args, **kwargs)
    return _wrapper

for name in [
        "__bool__",
        "__invert__", "__neg__",
        "__add__", "__radd__", "__sub__", "__rsub__",
        "__mul__", "__rmul__",
        "__mod__", "__rmod__", "__floordiv__", "__rfloordiv__",
        "__lshift__", "__rlshift__", "__rshift__", "__rrshift__",
        "__and__", "__rand__", "__xor__", "__rxor__", "__or__", "__ror__",
        "__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__",
        "__abs__", "__len__",
        "as_unsigned", "as_signed", "bool", "any", "all", "xor", "implies",
        "bit_select", "word_select", "matches",
        "shift_left", "shift_right", "rotate_left", "rotate_right", "eq"
        ]:
    setattr(Record, name, _valueproxy(name))

del _valueproxy
del name


# amaranth/hdl/xfrm.py

from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from collections.abc import Iterable

from .._utils import flatten, _ignore_deprecated
from .. import tracer
from .ast import *
from .ast import _StatementList
from .cd import *
from .ir import *
from .rec import *


__all__ = ["ValueVisitor", "ValueTransformer",
           "StatementVisitor", "StatementTransformer",
           "FragmentTransformer",
           "TransformedElaboratable",
           "DomainCollector", "DomainRenamer", "DomainLowerer",
           "SampleDomainInjector", "SampleLowerer",
           "SwitchCleaner", "LHSGroupAnalyzer", "LHSGroupFilter",
           "ResetInserter", "EnableInserter"]


class ValueVisitor(metaclass=ABCMeta):
    @abstractmethod
    def on_Const(self, value):
        pass # :nocov:

    @abstractmethod
    def on_AnyConst(self, value):
        pass # :nocov:

    @abstractmethod
    def on_AnySeq(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Signal(self, value):
        pass # :nocov:

    @abstractmethod
    def on_ClockSignal(self, value):
        pass # :nocov:

    @abstractmethod
    def on_ResetSignal(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Operator(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Slice(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Part(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Cat(self, value):
        pass # :nocov:

    @abstractmethod
    def on_ArrayProxy(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Sample(self, value):
        pass # :nocov:

    @abstractmethod
    def on_Initial(self, value):
        pass # :nocov:

    def on_unknown_value(self, value):
        raise TypeError("Cannot transform value {!r}".format(value)) # :nocov:

    def replace_value_src_loc(self, value, new_value):
        return True

    def on_value(self, value):
        if type(value) is Const:
            new_value = self.on_Const(value)
        elif type(value) is AnyConst:
            new_value = self.on_AnyConst(value)
        elif type(value) is AnySeq:
            new_value = self.on_AnySeq(value)
        elif isinstance(value, Signal):
            # Uses `isinstance()` and not `type() is` because amaranth.compat requires it.
            new_value = self.on_Signal(value)
        elif type(value) is ClockSignal:
            new_value = self.on_ClockSignal(value)
        elif type(value) is ResetSignal:
            new_value = self.on_ResetSignal(value)
        elif type(value) is Operator:
            new_value = self.on_Operator(value)
        elif type(value) is Slice:
            new_value = self.on_Slice(value)
        elif type(value) is Part:
            new_value = self.on_Part(value)
        elif type(value) is Cat:
            new_value = self.on_Cat(value)
        elif type(value) is ArrayProxy:
            new_value = self.on_ArrayProxy(value)
        elif type(value) is Sample:
            new_value = self.on_Sample(value)
        elif type(value) is Initial:
            new_value = self.on_Initial(value)
        else:
            new_value = self.on_unknown_value(value)
        if isinstance(new_value, Value) and self.replace_value_src_loc(value, new_value):
            new_value.src_loc = value.src_loc
        return new_value

    def __call__(self, value):
        return self.on_value(value)


class ValueTransformer(ValueVisitor):
    def on_Const(self, value):
        return value

    def on_AnyConst(self, value):
        return value

    def on_AnySeq(self, value):
        return value

    def on_Signal(self, value):
        return value

    def on_ClockSignal(self, value):
        return value

    def on_ResetSignal(self, value):
        return value

    def on_Operator(self, value):
        return Operator(value.operator, [self.on_value(o) for o in value.operands])

    def on_Slice(self, value):
        return Slice(self.on_value(value.value), value.start, value.stop)

    def on_Part(self, value):
        return Part(self.on_value(value.value), self.on_value(value.offset),
                    value.width, value.stride)

    def on_Cat(self, value):
        return Cat(self.on_value(o) for o in value.parts)

    def on_ArrayProxy(self, value):
        return ArrayProxy([self.on_value(elem) for elem in value._iter_as_values()],
                          self.on_value(value.index))

    def on_Sample(self, value):
        return Sample(self.on_value(value.value), value.clocks, value.domain)

    def on_Initial(self, value):
        return value


class StatementVisitor(metaclass=ABCMeta):
    @abstractmethod
    def on_Assign(self, stmt):
        pass # :nocov:

    @abstractmethod
    def on_Assert(self, stmt):
        pass # :nocov:

    @abstractmethod
    def on_Assume(self, stmt):
        pass # :nocov:

    @abstractmethod
    def on_Cover(self, stmt):
        pass # :nocov:

    @abstractmethod
    def on_Switch(self, stmt):
        pass # :nocov:

    @abstractmethod
    def on_statements(self, stmts):
        pass # :nocov:

    def on_unknown_statement(self, stmt):
        raise TypeError("Cannot transform statement {!r}".format(stmt)) # :nocov:

    def replace_statement_src_loc(self, stmt, new_stmt):
        return True

    def on_statement(self, stmt):
        if type(stmt) is Assign:
            new_stmt = self.on_Assign(stmt)
        elif type(stmt) is Assert:
            new_stmt = self.on_Assert(stmt)
        elif type(stmt) is Assume:
            new_stmt = self.on_Assume(stmt)
        elif type(stmt) is Cover:
            new_stmt = self.on_Cover(stmt)
        elif isinstance(stmt, Switch):
            # Uses `isinstance()` and not `type() is` because amaranth.compat requires it.
            new_stmt = self.on_Switch(stmt)
        elif isinstance(stmt, Iterable):
            new_stmt = self.on_statements(stmt)
        else:
            new_stmt = self.on_unknown_statement(stmt)
        if isinstance(new_stmt, Statement) and self.replace_statement_src_loc(stmt, new_stmt):
            new_stmt.src_loc = stmt.src_loc
            if isinstance(new_stmt, Switch) and isinstance(stmt, Switch):
                new_stmt.case_src_locs = stmt.case_src_locs
        if isinstance(new_stmt, Property):
            new_stmt._MustUse__used = True
        return new_stmt

    def __call__(self, stmt):
        return self.on_statement(stmt)


class StatementTransformer(StatementVisitor):
    def on_value(self, value):
        return value

    def on_Assign(self, stmt):
        return Assign(self.on_value(stmt.lhs), self.on_value(stmt.rhs))

    def on_Assert(self, stmt):
        return Assert(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en, name=stmt.name)

    def on_Assume(self, stmt):
        return Assume(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en, name=stmt.name)

    def on_Cover(self, stmt):
        return Cover(self.on_value(stmt.test), _check=stmt._check, _en=stmt._en, name=stmt.name)

    def on_Switch(self, stmt):
        cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
        return Switch(self.on_value(stmt.test), cases)

    def on_statements(self, stmts):
        return _StatementList(flatten(self.on_statement(stmt) for stmt in stmts))


class FragmentTransformer:
    def map_subfragments(self, fragment, new_fragment):
        for subfragment, name in fragment.subfragments:
            new_fragment.add_subfragment(self(subfragment), name)

    def map_ports(self, fragment, new_fragment):
        for port, dir in fragment.ports.items():
            new_fragment.add_ports(port, dir=dir)

    def map_named_ports(self, fragment, new_fragment):
        if hasattr(self, "on_value"):
            for name, (value, dir) in fragment.named_ports.items():
                new_fragment.named_ports[name] = self.on_value(value), dir
        else:
            new_fragment.named_ports = OrderedDict(fragment.named_ports.items())

    def map_domains(self, fragment, new_fragment):
        for domain in fragment.iter_domains():
            new_fragment.add_domains(fragment.domains[domain])

    def map_statements(self, fragment, new_fragment):
        if hasattr(self, "on_statement"):
            new_fragment.add_statements(map(self.on_statement, fragment.statements))
        else:
            new_fragment.add_statements(fragment.statements)

    def map_drivers(self, fragment, new_fragment):
        for domain, signal in fragment.iter_drivers():
            new_fragment.add_driver(signal, domain)

    def on_fragment(self, fragment):
        if isinstance(fragment, Instance):
            new_fragment = Instance(fragment.type)
            new_fragment.parameters = OrderedDict(fragment.parameters)
            self.map_named_ports(fragment, new_fragment)
        else:
            new_fragment = Fragment()
            new_fragment.flatten = fragment.flatten
        new_fragment.attrs = OrderedDict(fragment.attrs)
        self.map_ports(fragment, new_fragment)
        self.map_subfragments(fragment, new_fragment)
        self.map_domains(fragment, new_fragment)
        self.map_statements(fragment, new_fragment)
        self.map_drivers(fragment, new_fragment)
        return new_fragment

    def __call__(self, value, *, src_loc_at=0):
        if isinstance(value, Fragment):
            return self.on_fragment(value)
        elif isinstance(value, TransformedElaboratable):
            value._transforms_.append(self)
            return value
        elif hasattr(value, "elaborate"):
            value = TransformedElaboratable(value, src_loc_at=1 + src_loc_at)
            value._transforms_.append(self)
            return value
        else:
            raise AttributeError("Object {!r} cannot be elaborated".format(value))


class TransformedElaboratable(Elaboratable):
    def __init__(self, elaboratable, *, src_loc_at=0):
        assert hasattr(elaboratable, "elaborate")

        # Fields prefixed and suffixed with underscore to avoid as many conflicts with the inner
        # object as possible, since we're forwarding attribute requests to it.
        self._elaboratable_ = elaboratable
        self._transforms_   = []

    def __getattr__(self, attr):
        return getattr(self._elaboratable_, attr)

    def elaborate(self, platform):
        fragment = Fragment.get(self._elaboratable_, platform)
        for transform in self._transforms_:
            fragment = transform(fragment)
        return fragment


class DomainCollector(ValueVisitor, StatementVisitor):
    def __init__(self):
        self.used_domains = set()
        self.defined_domains = set()
        self._local_domains = set()

    def _add_used_domain(self, domain_name):
        if domain_name is None:
            return
        if domain_name in self._local_domains:
            return
        self.used_domains.add(domain_name)

    def on_ignore(self, value):
        pass

    on_Const = on_ignore
    on_AnyConst = on_ignore
    on_AnySeq = on_ignore
    on_Signal = on_ignore

    def on_ClockSignal(self, value):
        self._add_used_domain(value.domain)

    def on_ResetSignal(self, value):
        self._add_used_domain(value.domain)

    def on_Operator(self, value):
        for o in value.operands:
            self.on_value(o)

    def on_Slice(self, value):
        self.on_value(value.value)

    def on_Part(self, value):
        self.on_value(value.value)
        self.on_value(value.offset)

    def on_Cat(self, value):
        for o in value.parts:
            self.on_value(o)

    def on_ArrayProxy(self, value):
        for elem in value._iter_as_values():
            self.on_value(elem)
        self.on_value(value.index)

    def on_Sample(self, value):
        self.on_value(value.value)

    def on_Initial(self, value):
        pass

    def on_Assign(self, stmt):
        self.on_value(stmt.lhs)
        self.on_value(stmt.rhs)

    def on_property(self, stmt):
        self.on_value(stmt.test)

    on_Assert = on_property
    on_Assume = on_property
    on_Cover  = on_property

    def on_Switch(self, stmt):
        self.on_value(stmt.test)
        for stmts in stmt.cases.values():
            self.on_statement(stmts)

    def on_statements(self, stmts):
        for stmt in stmts:
            self.on_statement(stmt)

    def on_fragment(self, fragment):
        if isinstance(fragment, Instance):
            for name, (value, dir) in fragment.named_ports.items():
                self.on_value(value)

        old_local_domains, self._local_domains = self._local_domains, set(self._local_domains)
        for domain_name, domain in fragment.domains.items():
            if domain.local:
                self._local_domains.add(domain_name)
            else:
                self.defined_domains.add(domain_name)

        self.on_statements(fragment.statements)
        for domain_name in fragment.drivers:
            self._add_used_domain(domain_name)
        for subfragment, name in fragment.subfragments:
            self.on_fragment(subfragment)

        self._local_domains = old_local_domains

    def __call__(self, fragment):
        self.on_fragment(fragment)


class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer):
    def __init__(self, domain_map):
        if isinstance(domain_map, str):
            domain_map = {"sync": domain_map}
        for src, dst in domain_map.items():
            if src == "comb":
                raise ValueError("Domain '{}' may not be renamed".format(src))
            if dst == "comb":
                raise ValueError("Domain '{}' may not be renamed to '{}'".format(src, dst))
        self.domain_map = OrderedDict(domain_map)

    def on_ClockSignal(self, value):
        if value.domain in self.domain_map:
            return ClockSignal(self.domain_map[value.domain])
        return value

    def on_ResetSignal(self, value):
        if value.domain in self.domain_map:
            return ResetSignal(self.domain_map[value.domain],
                               allow_reset_less=value.allow_reset_less)
        return value

    def map_domains(self, fragment, new_fragment):
        for domain in fragment.iter_domains():
            cd = fragment.domains[domain]
            if domain in self.domain_map:
                if cd.name == domain:
                    # Rename the actual ClockDomain object.
                    cd.rename(self.domain_map[domain])
                else:
                    assert cd.name == self.domain_map[domain]
            new_fragment.add_domains(cd)

    def map_drivers(self, fragment, new_fragment):
        for domain, signals in fragment.drivers.items():
            if domain in self.domain_map:
                domain = self.domain_map[domain]
            for signal in signals:
                new_fragment.add_driver(self.on_value(signal), domain)


class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
    def __init__(self, domains=None):
        self.domains = domains

    def _resolve(self, domain, context):
        if domain not in self.domains:
            raise DomainError("Signal {!r} refers to nonexistent domain '{}'"
                              .format(context, domain))
        return self.domains[domain]

    def map_drivers(self, fragment, new_fragment):
        for domain, signal in fragment.iter_drivers():
            new_fragment.add_driver(self.on_value(signal), domain)

    def replace_value_src_loc(self, value, new_value):
        return not isinstance(value, (ClockSignal, ResetSignal))

    def on_ClockSignal(self, value):
        domain = self._resolve(value.domain, value)
        return domain.clk

    def on_ResetSignal(self, value):
        domain = self._resolve(value.domain, value)
        if domain.rst is None:
            if value.allow_reset_less:
                return Const(0)
            else:
                raise DomainError("Signal {!r} refers to reset of reset-less domain '{}'"
                                  .format(value, value.domain))
        return domain.rst

    def _insert_resets(self, fragment):
        for domain_name, signals in fragment.drivers.items():
            if domain_name is None:
                continue
            domain = fragment.domains[domain_name]
            if domain.rst is None:
                continue
            stmts = [signal.eq(Const(signal.reset, signal.width))
                     for signal in signals if not signal.reset_less]
            fragment.add_statements(Switch(domain.rst, {1: stmts}))

    def on_fragment(self, fragment):
        self.domains = fragment.domains
        new_fragment = super().on_fragment(fragment)
        self._insert_resets(new_fragment)
        return new_fragment


class SampleDomainInjector(ValueTransformer, StatementTransformer):
    def __init__(self, domain):
        self.domain = domain

    @_ignore_deprecated
    def on_Sample(self, value):
        if value.domain is not None:
            return value
        return Sample(value.value, value.clocks, self.domain)

    def __call__(self, stmts):
        return self.on_statement(stmts)


class SampleLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
    def __init__(self):
        self.initial = None
        self.sample_cache = None
        self.sample_stmts = None

    def _name_reset(self, value):
        if isinstance(value, Const):
            return "c${}".format(value.value), value.value
        elif isinstance(value, Signal):
            return "s${}".format(value.name), value.reset
        elif isinstance(value, ClockSignal):
            return "clk", 0
        elif isinstance(value, ResetSignal):
            return "rst", 1
        elif isinstance(value, Initial):
            return "init", 0 # Past(Initial()) produces 0, 1, 0, 0, ...
        else:
            raise NotImplementedError # :nocov:

    @_ignore_deprecated
    def on_Sample(self, value):
        if value in self.sample_cache:
            return self.sample_cache[value]

        sampled_value = self.on_value(value.value)
        if value.clocks == 0:
            sample = sampled_value
        else:
            assert value.domain is not None
            sampled_name, sampled_reset = self._name_reset(value.value)
            name = "$sample${}${}${}".format(sampled_name, value.domain, value.clocks)
            sample = Signal.like(value.value, name=name, reset_less=True, reset=sampled_reset)
            sample.attrs["amaranth.sample_reg"] = True

            prev_sample = self.on_Sample(Sample(sampled_value, value.clocks - 1, value.domain))
            if value.domain not in self.sample_stmts:
                self.sample_stmts[value.domain] = []
            self.sample_stmts[value.domain].append(sample.eq(prev_sample))

        self.sample_cache[value] = sample
        return sample

    def on_Initial(self, value):
        if self.initial is None:
            self.initial = Signal(name="init")
        return self.initial

    def map_statements(self, fragment, new_fragment):
        self.initial = None
        self.sample_cache = ValueDict()
        self.sample_stmts = OrderedDict()
        new_fragment.add_statements(map(self.on_statement, fragment.statements))
        for domain, stmts in self.sample_stmts.items():
            new_fragment.add_statements(stmts)
            for stmt in stmts:
                new_fragment.add_driver(stmt.lhs, domain)
        if self.initial is not None:
            new_fragment.add_subfragment(Instance("$initstate", o_Y=self.initial))


class SwitchCleaner(StatementVisitor):
    def on_ignore(self, stmt):
        return stmt

    on_Assign = on_ignore
    on_Assert = on_ignore
    on_Assume = on_ignore
    on_Cover  = on_ignore

    def on_Switch(self, stmt):
        cases = OrderedDict((k, self.on_statement(s)) for k, s in stmt.cases.items())
        if any(len(s) for s in cases.values()):
            return Switch(stmt.test, cases)

    def on_statements(self, stmts):
        stmts = flatten(self.on_statement(stmt) for stmt in stmts)
        return _StatementList(stmt for stmt in stmts if stmt is not None)


class LHSGroupAnalyzer(StatementVisitor):
    def __init__(self):
        self.signals = SignalDict()
        self.unions  = OrderedDict()

    def find(self, signal):
        if signal not in self.signals:
            self.signals[signal] = len(self.signals)
        group = self.signals[signal]
        while group in self.unions:
            group = self.unions[group]
        self.signals[signal] = group
        return group

    def unify(self, root, *leaves):
        root_group = self.find(root)
        for leaf in leaves:
            leaf_group = self.find(leaf)
            if root_group == leaf_group:
                continue
            self.unions[leaf_group] = root_group

    def groups(self):
        groups = OrderedDict()
        for signal in self.signals:
            group = self.find(signal)
            if group not in groups:
                groups[group] = SignalSet()
            groups[group].add(signal)
        return groups

    def on_Assign(self, stmt):
        lhs_signals = stmt._lhs_signals()
        if lhs_signals:
            self.unify(*stmt._lhs_signals())

    def on_property(self, stmt):
        lhs_signals = stmt._lhs_signals()
        if lhs_signals:
            self.unify(*stmt._lhs_signals())

    on_Assert = on_property
    on_Assume = on_property
    on_Cover  = on_property

    def on_Switch(self, stmt):
        for case_stmts in stmt.cases.values():
            self.on_statements(case_stmts)

    def on_statements(self, stmts):
        for stmt in stmts:
            self.on_statement(stmt)

    def __call__(self, stmts):
        self.on_statements(stmts)
        return self.groups()


class LHSGroupFilter(SwitchCleaner):
    def __init__(self, signals):
        self.signals = signals

    def on_Assign(self, stmt):
        # The invariant provided by LHSGroupAnalyzer is that all signals that ever appear together
        # on LHS are a part of the same group, so it is sufficient to check any of them.
        lhs_signals = stmt.lhs._lhs_signals()
        if lhs_signals:
            any_lhs_signal = next(iter(lhs_signals))
            if any_lhs_signal in self.signals:
                return stmt

    def on_property(self, stmt):
        any_lhs_signal = next(iter(stmt._lhs_signals()))
        if any_lhs_signal in self.signals:
            return stmt

    on_Assert = on_property
    on_Assume = on_property
    on_Cover  = on_property


class _ControlInserter(FragmentTransformer):
    def __init__(self, controls):
        self.src_loc = None
        if isinstance(controls, Value):
            controls = {"sync": controls}
        self.controls = OrderedDict(controls)

    def on_fragment(self, fragment):
        new_fragment = super().on_fragment(fragment)
        for domain, signals in fragment.drivers.items():
            if domain is None or domain not in self.controls:
                continue
            self._insert_control(new_fragment, domain, signals)
        return new_fragment

    def _insert_control(self, fragment, domain, signals):
        raise NotImplementedError # :nocov:

    def __call__(self, value, *, src_loc_at=0):
        self.src_loc = tracer.get_src_loc(src_loc_at=src_loc_at)
        return super().__call__(value, src_loc_at=1 + src_loc_at)


class ResetInserter(_ControlInserter):
    def _insert_control(self, fragment, domain, signals):
        stmts = [s.eq(Const(s.reset, s.width)) for s in signals if not s.reset_less]
        fragment.add_statements(Switch(self.controls[domain], {1: stmts}, src_loc=self.src_loc))


class EnableInserter(_ControlInserter):
    def _insert_control(self, fragment, domain, signals):
        stmts = [s.eq(s) for s in signals]
        fragment.add_statements(Switch(self.controls[domain], {0: stmts}, src_loc=self.src_loc))

    def on_fragment(self, fragment):
        new_fragment = super().on_fragment(fragment)
        if isinstance(new_fragment, Instance) and new_fragment.type in ("$memrd", "$memwr"):
            clk_port, clk_dir = new_fragment.named_ports["CLK"]
            if isinstance(clk_port, ClockSignal) and clk_port.domain in self.controls:
                en_port, en_dir = new_fragment.named_ports["EN"]
                en_port = Mux(self.controls[clk_port.domain], en_port, Const(0, len(en_port)))
                new_fragment.named_ports["EN"] = en_port, en_dir
        return new_fragment


# amaranth/lib/cdc.py

from .. import *


__all__ = ["FFSynchronizer", "AsyncFFSynchronizer", "ResetSynchronizer", "PulseSynchronizer"]


def _check_stages(stages):
    if not isinstance(stages, int) or stages < 1:
        raise TypeError("Synchronization stage count must be a positive integer, not {!r}"
                        .format(stages))
    if stages < 2:
        raise ValueError("Synchronization stage count may not safely be less than 2")


class FFSynchronizer(Elaboratable):
    """Resynchronise a signal to a different clock domain.

    Consists of a chain of flip-flops. Eliminates metastabilities at the output, but provides
    no other guarantee as to the safe domain-crossing of a signal.

    Parameters
    ----------
    i : Signal(n), in
        Signal to be resynchronised.
    o : Signal(n), out
        Signal connected to synchroniser output.
    o_domain : str
        Name of output clock domain.
    reset : int
        Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is ``True``,
        the :class:`FFSynchronizer` is still set to this value during initialization.
    reset_less : bool
        If ``True`` (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain``
        reset. See the note below for details.
    stages : int, >=2
        Number of synchronization stages between input and output. The lowest safe number is 2,
        with higher numbers reducing MTBF further, at the cost of increased latency.
    max_input_delay : None or float
        Maximum delay from the input signal's clock to the first synchronization stage, in seconds.
        If specified and the platform does not support it, elaboration will fail.


    .. note::

        :class:`FFSynchronizer` is non-resettable by default. Usually this is the safest option;
        on FPGAs the :class:`FFSynchronizer` will still be initialized to its ``reset`` value when
        the FPGA loads its configuration.

        However, in designs where the value of the :class:`FFSynchronizer` must be valid immediately
        after reset, consider setting ``reset_less`` to ``False`` if any of the following is true:

        - You are targeting an ASIC, or an FPGA that does not allow arbitrary initial flip-flop states;
        - Your design features warm (non-power-on) resets of ``o_domain``, so the one-time
          initialization at power on is insufficient;
        - Your design features a sequenced reset, and the :class:`FFSynchronizer` must maintain
          its reset value until ``o_domain`` reset specifically is deasserted.

        :class:`FFSynchronizer` is reset by the ``o_domain`` reset only.

    Platform overrides
    ------------------
    Define the ``get_ff_sync`` platform method to override the implementation of
    :class:`FFSynchronizer`, e.g. to instantiate library cells directly.
    """
    def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2,
                 max_input_delay=None):
        _check_stages(stages)

        self.i = i
        self.o = o

        self._reset      = reset
        self._reset_less = reset_less
        self._o_domain   = o_domain
        self._stages     = stages

        self._max_input_delay = max_input_delay

    def elaborate(self, platform):
        if hasattr(platform, "get_ff_sync"):
            return platform.get_ff_sync(self)

        if self._max_input_delay is not None:
            raise NotImplementedError("Platform '{}' does not support constraining input delay "
                                      "for FFSynchronizer"
                                      .format(type(platform).__name__))

        m = Module()
        flops = [Signal(self.i.shape(), name="stage{}".format(index),
                        reset=self._reset, reset_less=self._reset_less)
                 for index in range(self._stages)]
        for i, o in zip((self.i, *flops), flops):
            m.d[self._o_domain] += o.eq(i)
        m.d.comb += self.o.eq(flops[-1])
        return m


class AsyncFFSynchronizer(Elaboratable):
    """Synchronize deassertion of an asynchronous signal.

    The signal driven by the :class:`AsyncFFSynchronizer` is asserted asynchronously and deasserted
    synchronously, eliminating metastability during deassertion.

    This synchronizer is primarily useful for resets and reset-like signals.

    Parameters
    ----------
    i : Signal(1), in
        Asynchronous input signal, to be synchronized.
    o : Signal(1), out
        Synchronously released output signal.
    o_domain : str
        Name of clock domain to synchronize to.
    stages : int, >=2
        Number of synchronization stages between input and output. The lowest safe number is 2,
        with higher numbers reducing MTBF further, at the cost of increased deassertion latency.
    async_edge : str
        The edge of the input signal which causes the output to be set. Must be one of "pos" or "neg".
    max_input_delay : None or float
        Maximum delay from the input signal's clock to the first synchronization stage, in seconds.
        If specified and the platform does not support it, elaboration will fail.

    Platform overrides
    ------------------
    Define the ``get_async_ff_sync`` platform method to override the implementation of
    :class:`AsyncFFSynchronizer`, e.g. to instantiate library cells directly.
    """
    def __init__(self, i, o, *, o_domain="sync", stages=2, async_edge="pos", max_input_delay=None):
        _check_stages(stages)

        if len(i) != 1:
            raise ValueError("AsyncFFSynchronizer input width must be 1, not {}"
                             .format(len(i)))
        if len(o) != 1:
            raise ValueError("AsyncFFSynchronizer output width must be 1, not {}"
                             .format(len(o)))

        if async_edge not in ("pos", "neg"):
            raise ValueError("AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', "
                             "not {!r}"
                             .format(async_edge))

        self.i = i
        self.o = o

        self._o_domain = o_domain
        self._stages = stages

        self._edge = async_edge

        self._max_input_delay = max_input_delay

    def elaborate(self, platform):
        if hasattr(platform, "get_async_ff_sync"):
            return platform.get_async_ff_sync(self)

        if self._max_input_delay is not None:
            raise NotImplementedError("Platform '{}' does not support constraining input delay "
                                      "for AsyncFFSynchronizer"
                                      .format(type(platform).__name__))

        m = Module()
        m.domains += ClockDomain("async_ff", async_reset=True, local=True)
        flops = [Signal(1, name="stage{}".format(index), reset=1)
                 for index in range(self._stages)]
        for i, o in zip((0, *flops), flops):
            m.d.async_ff += o.eq(i)

        if self._edge == "pos":
            m.d.comb += ResetSignal("async_ff").eq(self.i)
        else:
            m.d.comb += ResetSignal("async_ff").eq(~self.i)

        m.d.comb += [
            ClockSignal("async_ff").eq(ClockSignal(self._o_domain)),
            self.o.eq(flops[-1])
        ]

        return m


class ResetSynchronizer(Elaboratable):
    """Synchronize deassertion of a clock domain reset.

    The reset of the clock domain driven by the :class:`ResetSynchronizer` is asserted
    asynchronously and deasserted synchronously, eliminating metastability during deassertion.

    The driven clock domain could use a reset that is asserted either synchronously or
    asynchronously; a reset is always deasserted synchronously. A domain with an asynchronously
    asserted reset is useful if the clock of the domain may be gated, yet the domain still
    needs to be reset promptly; otherwise, synchronously asserted reset (the default) should
    be used.

    Parameters
    ----------
    arst : Signal(1), in
        Asynchronous reset signal, to be synchronized.
    domain : str
        Name of clock domain to reset.
    stages : int, >=2
        Number of synchronization stages between input and output. The lowest safe number is 2,
        with higher numbers reducing MTBF further, at the cost of increased deassertion latency.
    max_input_delay : None or float
        Maximum delay from the input signal's clock to the first synchronization stage, in seconds.
        If specified and the platform does not support it, elaboration will fail.

    Platform overrides
    ------------------
    Define the ``get_reset_sync`` platform method to override the implementation of
    :class:`ResetSynchronizer`, e.g. to instantiate library cells directly.
    """
    def __init__(self, arst, *, domain="sync", stages=2, max_input_delay=None):
        _check_stages(stages)

        self.arst = arst

        self._domain = domain
        self._stages = stages

        self._max_input_delay = max_input_delay

    def elaborate(self, platform):
        return AsyncFFSynchronizer(self.arst, ResetSignal(self._domain), o_domain=self._domain,
                stages=self._stages, max_input_delay=self._max_input_delay)


class PulseSynchronizer(Elaboratable):
    """A one-clock pulse on the input produces a one-clock pulse on the output.

    If the output clock is faster than the input clock, then the input may be safely asserted at
    100% duty cycle. Otherwise, if the clock ratio is ``n``:1, the input may be asserted at most
    once in every ``n`` input clocks, else pulses may be dropped. Other than this there is
    no constraint on the ratio of input and output clock frequency.

    Parameters
    ----------
    i_domain : str
        Name of input clock domain.
    o_domain : str
        Name of output clock domain.
    stages : int, >=2
        Number of synchronization stages between input and output. The lowest safe number is 2,
        with higher numbers reducing MTBF further, at the cost of increased deassertion latency.
    """
    def __init__(self, i_domain, o_domain, *, stages=2):
        _check_stages(stages)

        self.i = Signal()
        self.o = Signal()

        self._i_domain = i_domain
        self._o_domain = o_domain
        self._stages = stages

    def elaborate(self, platform):
        m = Module()

        i_toggle = Signal()
        o_toggle = Signal()
        r_toggle = Signal()
        ff_sync = m.submodules.ff_sync = \
            FFSynchronizer(i_toggle, o_toggle, o_domain=self._o_domain, stages=self._stages)

        m.d[self._i_domain] += i_toggle.eq(i_toggle ^ self.i)
        m.d[self._o_domain] += r_toggle.eq(o_toggle)
        m.d.comb += self.o.eq(o_toggle ^ r_toggle)

        return m


# amaranth/lib/coding.py

from .. import *


__all__ = [
    "Encoder", "Decoder",
    "PriorityEncoder", "PriorityDecoder",
    "GrayEncoder", "GrayDecoder",
]


class Encoder(Elaboratable):
    """Encode one-hot to binary.

    If one bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the asserted bit.
    Otherwise, ``n`` is high and ``o`` is ``0``.

    Parameters
    ----------
    width : int
        Bit width of the input

    Attributes
    ----------
    i : Signal(width), in
        One-hot input.
    o : Signal(range(width)), out
        Encoded natural binary.
    n : Signal, out
        Invalid: either none or multiple input bits are asserted.
    """
    def __init__(self, width):
        self.width = width

        self.i = Signal(width)
        self.o = Signal(range(width))
        self.n = Signal()

    def elaborate(self, platform):
        m = Module()
        with m.Switch(self.i):
            for j in range(self.width):
                with m.Case(1 << j):
                    m.d.comb += self.o.eq(j)
            with m.Case():
                m.d.comb += self.n.eq(1)
        return m


class PriorityEncoder(Elaboratable):
    """Priority encode requests to binary.

    If any bit in ``i`` is asserted, ``n`` is low and ``o`` indicates the least significant
    asserted bit.
    Otherwise, ``n`` is high and ``o`` is ``0``.

    Parameters
    ----------
    width : int
        Bit width of the input.

    Attributes
    ----------
    i : Signal(width), in
        Input requests.
    o : Signal(range(width)), out
        Encoded natural binary.
    n : Signal, out
        Invalid: no input bits are asserted.
    """
    def __init__(self, width):
        self.width = width

        self.i = Signal(width)
        self.o = Signal(range(width))
        self.n = Signal()

    def elaborate(self, platform):
        m = Module()
        for j in reversed(range(self.width)):
            with m.If(self.i[j]):
                m.d.comb += self.o.eq(j)
        m.d.comb += self.n.eq(self.i == 0)
        return m


class Decoder(Elaboratable):
    """Decode binary to one-hot.

    If ``n`` is low, only the ``i``-th bit in ``o`` is asserted.
    If ``n`` is high, ``o`` is ``0``.

    Parameters
    ----------
    width : int
        Bit width of the output.

    Attributes
    ----------
    i : Signal(range(width)), in
        Input binary.
    o : Signal(width), out
        Decoded one-hot.
    n : Signal, in
        Invalid, no output bits are to be asserted.
    """
    def __init__(self, width):
        self.width = width

        self.i = Signal(range(width))
        self.n = Signal()
        self.o = Signal(width)

    def elaborate(self, platform):
        m = Module()
        with m.Switch(self.i):
            for j in range(len(self.o)):
                with m.Case(j):
                    m.d.comb += self.o.eq(1 << j)
        with m.If(self.n):
            m.d.comb += self.o.eq(0)
        return m


class PriorityDecoder(Decoder):
    """Decode binary to priority request.

    Identical to :class:`Decoder`.
    """


class GrayEncoder(Elaboratable):
    """Encode binary to Gray code.

    Parameters
    ----------
    width : int
        Bit width.

    Attributes
    ----------
    i : Signal(width), in
        Natural binary input.
    o : Signal(width), out
        Encoded Gray code.
    """
    def __init__(self, width):
        self.width = width

        self.i = Signal(width)
        self.o = Signal(width)

    def elaborate(self, platform):
        m = Module()
        m.d.comb += self.o.eq(self.i ^ self.i[1:])
        return m


class GrayDecoder(Elaboratable):
    """Decode Gray code to binary.

    Parameters
    ----------
    width : int
        Bit width.

    Attributes
    ----------
    i : Signal(width), in
        Gray code input.
    o : Signal(width), out
        Decoded natural binary.
    """
    def __init__(self, width):
        self.width = width

        self.i = Signal(width)
        self.o = Signal(width)

    def elaborate(self, platform):
        m = Module()
        rhs = Const(0)
        for i in reversed(range(self.width)):
            rhs = rhs ^ self.i[i]
            m.d.comb += self.o[i].eq(rhs)
        return m


# amaranth/lib/data.py

from abc import ABCMeta, abstractmethod
from collections.abc import Mapping, Sequence
import warnings

from amaranth.hdl import *
from amaranth.hdl.ast import ShapeCastable, ValueCastable


__all__ = [
    "Field", "Layout", "StructLayout", "UnionLayout", "ArrayLayout", "FlexibleLayout",
    "View", "Struct", "Union",
]


class Field:
    """Description of a data field.

    The :class:`Field` class specifies the signedness and bit positions of a field in
    an Amaranth value.

    :class:`Field` objects are immutable.

    Attributes
    ----------
    shape : :ref:`shape-castable <lang-shapecasting>`
        Shape of the field. When initialized or assigned, the object is stored as-is.
    offset : :class:`int`, >=0
        Index of the least significant bit of the field.
    """
    def __init__(self, shape, offset):
        try:
            Shape.cast(shape)
        except TypeError as e:
            raise TypeError("Field shape must be a shape-castable object, not {!r}"
                            .format(shape)) from e
        if not isinstance(offset, int) or offset < 0:
            raise TypeError("Field offset must be a non-negative integer, not {!r}"
                            .format(offset))
        self._shape  = shape
        self._offset = offset

    @property
    def shape(self):
        return self._shape

    @property
    def offset(self):
        return self._offset

    @property
    def width(self):
        """Width of the field.

        This property should be used over ``self.shape.width`` because ``self.shape`` can be
        an arbitrary :ref:`shape-castable <lang-shapecasting>` object, which may not have
        a ``width`` property.

        Returns
        -------
        :class:`int`
            ``Shape.cast(self.shape).width``
        """
        return Shape.cast(self.shape).width

    def __eq__(self, other):
        """Compare fields.

        Two fields are equal if they have the same shape and offset.
        """
        return (isinstance(other, Field) and
                Shape.cast(self._shape) == Shape.cast(other.shape) and
                self._offset == other.offset)

    def __repr__(self):
        return f"Field({self._shape!r}, {self._offset})"


class Layout(ShapeCastable, metaclass=ABCMeta):
    """Description of a data layout.

    The :ref:`shape-castable <lang-shapecasting>` :class:`Layout` interface associates keys
    (string names or integer indexes) with fields, giving identifiers to spans of bits in
    an Amaranth value.

    It is an abstract base class; :class:`StructLayout`, :class:`UnionLayout`,
    :class:`ArrayLayout`, and :class:`FlexibleLayout` implement concrete layout rules.
    New layout rules can be defined by inheriting from this class.

    Like all other shape-castable objects, all layouts are immutable. New classes deriving from
    :class:`Layout` must preserve this invariant.
    """

    @staticmethod
    def cast(obj):
        """Cast a :ref:`shape-castable <lang-shapecasting>` object to a layout.

        This method performs a subset of the operations done by :meth:`Shape.cast`; it will
        recursively call ``.as_shape()``, but only until a layout is returned.

        Raises
        ------
        TypeError
            If ``obj`` cannot be converted to a :class:`Layout` instance.
        RecursionError
            If ``obj.as_shape()`` returns ``obj``.
        """
        while isinstance(obj, ShapeCastable):
            if isinstance(obj, Layout):
                return obj
            new_obj = obj.as_shape()
            if new_obj is obj:
                break
            obj = new_obj
        Shape.cast(obj) # delegate non-layout-specific error handling to Shape
        raise TypeError("Object {!r} cannot be converted to a data layout"
                        .format(obj))

    @abstractmethod
    def __iter__(self):
        """Iterate fields in the layout.

        Yields
        ------
        :class:`str` or :class:`int`
            Key (either name or index) for accessing the field.
        :class:`Field`
            Description of the field.
        """

    @abstractmethod
    def __getitem__(self, key):
        """Retrieve a field from the layout.

        Returns
        -------
        :class:`Field`
            The field associated with ``key``.

        Raises
        ------
        KeyError
            If there is no field associated with ``key``.
        """

    @property
    @abstractmethod
    def size(self):
        """Size of the layout.

        Returns
        -------
        :class:`int`
            The amount of bits required to store every field in the layout.
        """

    def as_shape(self):
        """Shape of the layout.

        Returns
        -------
        :class:`Shape`
            ``unsigned(self.size)``
        """
        return unsigned(self.size)

    def __eq__(self, other):
        """Compare layouts.

        Two layouts are equal if they have the same size and the same fields under the same names.
        The order of the fields is not considered.
        """
        while isinstance(other, ShapeCastable) and not isinstance(other, Layout):
            new_other = other.as_shape()
            if new_other is other:
                break
            other = new_other
        return (isinstance(other, Layout) and self.size == other.size and
                dict(iter(self)) == dict(iter(other)))

    def __call__(self, target):
        """Create a view into a target.

        When a :class:`Layout` is used as the shape of a :class:`Field` and accessed through
        a :class:`View`, this method is used to wrap the slice of the underlying value into
        another view with this layout.

        Returns
        -------
        :class:`View`
            ``View(self, target)``
        """
        return View(self, target)

    def const(self, init):
        """Convert a constant initializer to a constant.

        Converts ``init``, which may be a sequence or a mapping of field values, to a constant.

        Returns
        -------
        :class:`Const`
            A constant that has the same value as a view with this layout that was initialized with
            an all-zero value and had every field assigned to the corresponding value in the order
            in which they appear in ``init``.
        """
        if init is None:
            iterator = iter(())
        elif isinstance(init, Mapping):
            iterator = init.items()
        elif isinstance(init, Sequence):
            iterator = enumerate(init)
        else:
            raise TypeError("Layout constant initializer must be a mapping or a sequence, not {!r}"
                            .format(init))

        int_value = 0
        for key, key_value in iterator:
            field = self[key]
            cast_field_shape = Shape.cast(field.shape)
            if isinstance(field.shape, ShapeCastable):
                key_value = Const.cast(field.shape.const(key_value))
                if key_value.shape() != cast_field_shape:
                    raise ValueError("Constant returned by {!r}.const() must have the shape that "
                                     "it casts to, {!r}, and not {!r}"
                                     .format(field.shape, cast_field_shape,
                                             key_value.shape()))
            elif not isinstance(key_value, Const):
                key_value = Const(key_value, cast_field_shape)
            mask = ((1 << cast_field_shape.width) - 1) << field.offset
            int_value &= ~mask
            int_value |= (key_value.value << field.offset) & mask
        return Const(int_value, self.as_shape())


class StructLayout(Layout):
    """Description of a structure layout.

    The fields of a structure layout follow one another without any gaps, and the size of
    a structure layout is the sum of the sizes of its members.

    For example, the following layout of a 16-bit value:

    .. image:: _images/data/struct_layout.svg

    can be described with:

    .. testcode::

        data.StructLayout({
            "first":  3,
            "second": 7,
            "third":  6
        })

    .. note::

        Structures that have padding can be described with a :class:`FlexibleLayout`. Alternately,
        padding can be added to the layout as fields called ``_1``, ``_2``, and so on. These fields
        won't be accessible as attributes or by using indexing.

    Attributes
    ----------
    members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
        Dictionary of structure members.
    """

    def __init__(self, members):
        offset = 0
        self._fields = {}
        if not isinstance(members, Mapping):
            raise TypeError("Struct layout members must be provided as a mapping, not {!r}"
                            .format(members))
        for key, shape in members.items():
            if not isinstance(key, str):
                raise TypeError("Struct layout member name must be a string, not {!r}"
                                .format(key))
            try:
                cast_shape = Shape.cast(shape)
            except TypeError as e:
                raise TypeError("Struct layout member shape must be a shape-castable object, "
                                "not {!r}"
                                .format(shape)) from e
            self._fields[key] = Field(shape, offset)
            offset += cast_shape.width

    @property
    def members(self):
        return {key: field.shape for key, field in self._fields.items()}

    def __iter__(self):
        return iter(self._fields.items())

    def __getitem__(self, key):
        return self._fields[key]

    @property
    def size(self):
        """Size of the structure layout.

        Returns
        -------
        :class:`int`
            Index of the most significant bit of the *last* field plus one; or zero if there are
            no fields.
        """
        return max((field.offset + field.width for field in self._fields.values()), default=0)

    def __repr__(self):
        return f"StructLayout({self.members!r})"


class UnionLayout(Layout):
    """Description of a union layout.

    The fields of a union layout all start from bit 0, and the size of a union layout is the size
    of the largest of its members.

    For example, the following layout of a 7-bit value:

    .. image:: _images/data/union_layout.svg

    can be described with:

    .. testcode::

        data.UnionLayout({
            "first":  3,
            "second": 7,
            "third":  6
        })

    Attributes
    ----------
    members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
        Dictionary of union members.
    """
    def __init__(self, members):
        self._fields = {}
        if not isinstance(members, Mapping):
            raise TypeError("Union layout members must be provided as a mapping, not {!r}"
                            .format(members))
        for key, shape in members.items():
            if not isinstance(key, str):
                raise TypeError("Union layout member name must be a string, not {!r}"
                                .format(key))
            try:
                cast_shape = Shape.cast(shape)
            except TypeError as e:
                raise TypeError("Union layout member shape must be a shape-castable object, "
                                "not {!r}"
                                .format(shape)) from e
            self._fields[key] = Field(shape, 0)

    @property
    def members(self):
        return {key: field.shape for key, field in self._fields.items()}

    def __iter__(self):
        return iter(self._fields.items())

    def __getitem__(self, key):
        return self._fields[key]

    @property
    def size(self):
        """Size of the union layout.

        Returns
        -------
        :class:`int`
            Index of the most significant bit of the *largest* field plus one; or zero if there are
            no fields.
        """
        return max((field.width for field in self._fields.values()), default=0)

    def const(self, init):
        if init is not None and len(init) > 1:
            raise ValueError("Initializer for at most one field can be provided for "
                             "a union layout (specified: {})"
                             .format(", ".join(init.keys())))
        return super().const(init)

    def __repr__(self):
        return f"UnionLayout({self.members!r})"


class ArrayLayout(Layout):
    """Description of an array layout.

    The fields of an array layout follow one another without any gaps, and the size of an array
    layout is the size of its element multiplied by its length.

    For example, the following layout of a 16-bit value:

    .. image:: _images/data/array_layout.svg

    can be described with:

    .. testcode::

        data.ArrayLayout(unsigned(4), 4)

    .. note::

        Arrays that have padding can be described with a :class:`FlexibleLayout`.

    Attributes
    ----------
    elem_shape : :ref:`shape-castable <lang-shapecasting>`
        Shape of an individual element.
    length : :class:`int`
        Amount of elements.
    """
    def __init__(self, elem_shape, length):
        try:
            Shape.cast(elem_shape)
        except TypeError as e:
            raise TypeError("Array layout element shape must be a shape-castable object, "
                            "not {!r}"
                            .format(elem_shape)) from e
        if not isinstance(length, int) or length < 0:
            raise TypeError("Array layout length must be a non-negative integer, not {!r}"
                            .format(length))
        self._elem_shape = elem_shape
        self._length     = length

    @property
    def elem_shape(self):
        return self._elem_shape

    @property
    def length(self):
        return self._length

    def __iter__(self):
        offset = 0
        for index in range(self._length):
            yield index, Field(self._elem_shape, offset)
            offset += Shape.cast(self._elem_shape).width

    def __getitem__(self, key):
        if isinstance(key, int):
            if key not in range(-self._length, self._length):
                # Layout's interface requires us to raise KeyError, not IndexError
                raise KeyError(key)
            if key < 0:
                key += self._length
            return Field(self._elem_shape, key * Shape.cast(self._elem_shape).width)
        raise TypeError("Cannot index array layout with {!r}".format(key))

    @property
    def size(self):
        """Size of the array layout.

        Returns
        -------
        :class:`int`
            Size of an individual element multiplied by their amount.
        """
        return Shape.cast(self._elem_shape).width * self.length

    def __repr__(self):
        return f"ArrayLayout({self._elem_shape!r}, {self.length})"


class FlexibleLayout(Layout):
    """Description of a flexible layout.

    The fields of a flexible layout can be located arbitrarily, and its size is explicitly defined.

    For example, the following layout of a 16-bit value:

    .. image:: _images/data/flexible_layout.svg

    can be described with:

    .. testcode::

        data.FlexibleLayout(16, {
            "first":  data.Field(unsigned(3), 1),
            "second": data.Field(unsigned(7), 0),
            "third":  data.Field(unsigned(6), 10),
            0:        data.Field(unsigned(1), 14)
        })

    Both strings and integers can be used as names of flexible layout fields, so flexible layouts
    can be used to describe structures and arrays with arbitrary padding.

    Attributes
    ----------
    size : :class:`int`
        Size of the layout.
    fields : mapping of :class:`str` or :class:`int` to :class:`Field`
        Fields defined in the layout.
    """
    def __init__(self, size, fields):
        if not isinstance(size, int) or size < 0:
            raise TypeError("Flexible layout size must be a non-negative integer, not {!r}"
                            .format(size))
        if not isinstance(fields, Mapping):
            raise TypeError("Flexible layout fields must be provided as a mapping, not {!r}"
                            .format(fields))
        self._size = size
        self._fields = {}
        for key, field in fields.items():
            if not isinstance(key, (int, str)) or (isinstance(key, int) and key < 0):
                raise TypeError("Flexible layout field name must be a non-negative integer or "
                                "a string, not {!r}"
                                .format(key))
            if not isinstance(field, Field):
                raise TypeError("Flexible layout field value must be a Field instance, not {!r}"
                                .format(field))
            if field.offset + field.width > size:
                raise ValueError("Flexible layout field '{}' ends at bit {}, exceeding "
                                 "the size of {} bit(s)"
                                 .format(key, field.offset + field.width, size))
            self._fields[key] = field

    @property
    def size(self):
        """:meta private:""" # work around Sphinx bug
        return self._size

    @property
    def fields(self):
        return {**self._fields}

    def __iter__(self):
        return iter(self._fields.items())

    def __getitem__(self, key):
        if isinstance(key, (int, str)):
            return self._fields[key]
        raise TypeError("Cannot index flexible layout with {!r}".format(key))

    def __repr__(self):
        return f"FlexibleLayout({self._size}, {self._fields!r})"


class View(ValueCastable):
    """A value viewed through the lens of a layout.

    The :ref:`value-castable <lang-valuecasting>` class :class:`View` provides access to the fields
    of an underlying Amaranth value via the names or indexes defined in the provided layout.

    Creating a view
    ###############

    A view must be created using an explicitly provided layout and target. To create a new :class:`Signal` that is wrapped in a :class:`View` with a given ``layout``, use ``Signal(layout, ...)``, which for a :class:`Layout` is equivalent to ``View(layout, Signal(...))``.

    Accessing a view
    ################

    Slicing a view or accessing its attributes returns a part of the underlying value
    corresponding to the field with that index or name, which is itself either a value or
    a value-castable object. If the shape of the field is a :class:`Layout`, it will be
    a :class:`View`; if it is a class deriving from :class:`Struct` or :class:`Union`, it
    will be an instance of that data class; if it is another
    :ref:`shape-castable <lang-shapecasting>` object implementing ``__call__``, it will be
    the result of calling that method.

    Slicing a view whose layout is an :class:`ArrayLayout` can be done with an index that is
    an Amaranth value instead of a constant integer. The returned element is chosen dynamically
    in that case.

    Custom view classes
    ###################

    The :class:`View` class can be inherited from to define additional properties or methods on
    a view. The only two names that are reserved on instances of :class:`View` are :meth:`as_value`
    and :meth:`eq`, leaving the rest to the developer. The :class:`Struct` and :class:`Union`
    classes provided in this module are subclasses of :class:`View` that also provide a concise way
    to define a layout.
    """
    def __init__(self, layout, target):
        try:
            cast_layout = Layout.cast(layout)
        except TypeError as e:
            raise TypeError("View layout must be a layout, not {!r}"
                            .format(layout)) from e
        try:
            cast_target = Value.cast(target)
        except TypeError as e:
            raise TypeError("View target must be a value-castable object, not {!r}"
                            .format(target)) from e
        if len(cast_target) != cast_layout.size:
            raise ValueError("View target is {} bit(s) wide, which is not compatible with "
                             "the {} bit(s) wide view layout"
                             .format(len(cast_target), cast_layout.size))
        for name, field in cast_layout:
            if isinstance(name, str) and name[0] != "_" and hasattr(type(self), name):
                warnings.warn("View layout includes a field {!r} that will be shadowed by "
                              "the view attribute '{}.{}.{}'"
                              .format(name, type(self).__module__, type(self).__qualname__, name),
                              SyntaxWarning, stacklevel=1)
        self.__orig_layout = layout
        self.__layout = cast_layout
        self.__target = cast_target

    def shape(self):
        """Get layout of this view.

        Returns
        -------
        :class:`Layout`
            The ``layout`` provided when constructing the view.
        """
        return self.__orig_layout

    @ValueCastable.lowermethod
    def as_value(self):
        """Get underlying value.

        Returns
        -------
        :class:`Value`
            The ``target`` provided when constructing the view, or the :class:`Signal` that
            was created.
        """
        return self.__target

    def eq(self, other):
        """Assign to the underlying value.

        Returns
        -------
        :class:`Assign`
            ``self.as_value().eq(other)``
        """
        return self.as_value().eq(other)

    def __getitem__(self, key):
        """Slice the underlying value.

        A field corresponding to ``key`` is looked up in the layout. If the field's shape is
        a shape-castable object that has a ``__call__`` method, it is called and the result is
        returned. Otherwise, ``as_shape`` is called repeatedly on the shape until either an object
        with a ``__call__`` method is reached, or a ``Shape`` is returned. In the latter case,
        returns an unspecified Amaranth expression with the right shape.

        Arguments
        ---------
        key : :class:`str` or :class:`int` or :class:`ValueCastable`
            Name or index of a field.

        Returns
        -------
        :class:`Value` or :class:`ValueCastable`, inout
            A slice of the underlying value defined by the field.

        Raises
        ------
        KeyError
            If the layout does not define a field corresponding to ``key``.
        TypeError
            If ``key`` is a value-castable object, but the layout of the view is not
            a :class:`ArrayLayout`.
        TypeError
            If ``ShapeCastable.__call__`` does not return a value or a value-castable object.
        """
        if isinstance(self.__layout, ArrayLayout):
            if not isinstance(key, (int, Value, ValueCastable)):
                raise TypeError("Views with array layout may only be indexed with an integer "
                                "or a value, not {!r}"
                                .format(key))
            shape = self.__layout.elem_shape
            value = self.__target.word_select(key, Shape.cast(self.__layout.elem_shape).width)
        else:
            if isinstance(key, (Value, ValueCastable)):
                raise TypeError("Only views with array layout, not {!r}, may be indexed "
                                "with a value"
                                .format(self.__layout))
            field = self.__layout[key]
            shape = field.shape
            value = self.__target[field.offset:field.offset + field.width]
        # Field guarantees that the shape-castable object is well-formed, so there is no need
        # to handle erroneous cases here.
        if isinstance(shape, ShapeCastable):
            value = shape(value)
            if not isinstance(value, (Value, ValueCastable)):
                raise TypeError("{!r}.__call__() must return a value or "
                                "a value-castable object, not {!r}"
                                .format(shape, value))
            return value
        if Shape.cast(shape).signed:
            return value.as_signed()
        else:
            return value

    def __getattr__(self, name):
        """Access a field of the underlying value.

        Returns ``self[name]``.

        Raises
        ------
        AttributeError
            If the layout does not define a field called ``name``, or if ``name`` starts with
            an underscore.
        """
        if isinstance(self.__layout, ArrayLayout):
            raise AttributeError("View of {!r} with an array layout does not have fields"
                                 .format(self.__target))
        try:
            item = self[name]
        except KeyError:
            raise AttributeError("View of {!r} does not have a field {!r}; "
                                 "did you mean one of: {}?"
                                 .format(self.__target, name,
                                         ", ".join(repr(name)
                                                   for name, field in self.__layout)))
        if name.startswith("_"):
            raise AttributeError("View of {!r} field {!r} has a reserved name and may only be "
                                 "accessed by indexing"
                                 .format(self.__target, name))
        return item


class _AggregateMeta(ShapeCastable, type):
    def __new__(metacls, name, bases, namespace):
        if "__annotations__" not in namespace:
            # This is a base class without its own layout. It is not shape-castable, and cannot
            # be instantiated. It can be used to share behavior.
            return type.__new__(metacls, name, bases, namespace)
        elif all(not hasattr(base, "_AggregateMeta__layout") for base in bases):
            # This is a leaf class with its own layout. It is shape-castable and can
            # be instantiated. It can also be subclassed, and used to share layout and behavior.
            layout  = dict()
            default = dict()
            for name in {**namespace["__annotations__"]}:
                try:
                    Shape.cast(namespace["__annotations__"][name])
                except TypeError:
                    # Not a shape-castable annotation; leave as-is.
                    continue
                layout[name] = namespace["__annotations__"].pop(name)
                if name in namespace:
                    default[name] = namespace.pop(name)
            cls = type.__new__(metacls, name, bases, namespace)
            if cls.__layout_cls is UnionLayout:
                if len(default) > 1:
                    raise ValueError("Reset value for at most one field can be provided for "
                                     "a union class (specified: {})"
                                     .format(", ".join(default.keys())))
            cls.__layout  = cls.__layout_cls(layout)
            cls.__default = default
            return cls
        else:
            # This is a class that has a base class with a layout and annotations. Such a class
            # is not well-formed.
            raise TypeError("Aggregate class '{}' must either inherit or specify a layout, "
                            "not both"
                            .format(name))

    def as_shape(cls):
        if not hasattr(cls, "_AggregateMeta__layout"):
            raise TypeError("Aggregate class '{}.{}' does not have a defined shape"
                            .format(cls.__module__, cls.__qualname__))
        return cls.__layout

    def __call__(cls, target):
        # This method exists to pass the override check done by ShapeCastable.
        return super().__call__(cls, target)

    def const(cls, init):
        if cls.__layout_cls is UnionLayout:
            if init is not None and len(init) > 1:
                raise ValueError("Initializer for at most one field can be provided for "
                                 "a union class (specified: {})"
                                 .format(", ".join(init.keys())))
            return cls.as_shape().const(init or cls.__default)
        else:
            fields = cls.__default.copy()
            fields.update(init or {})
            return cls.as_shape().const(fields)


class Struct(View, metaclass=_AggregateMeta):
    """Structures defined with annotations.

    The :class:`Struct` base class is a subclass of :class:`View` that provides a concise way
    to describe the structure layout and reset values for the fields using Python
    :term:`variable annotations <python:variable annotation>`.

    Any annotations containing :ref:`shape-castable <lang-shapecasting>` objects are used,
    in the order in which they appear in the source code, to construct a :class:`StructLayout`.
    The values assigned to such annotations are used to populate the reset value of the signal
    created by the view. Any other annotations are kept as-is.

    .. testsetup::

        from amaranth import *
        from amaranth.lib.data import *

    As an example, a structure for `IEEE 754 single-precision floating-point format
    <https://en.wikipedia.org/wiki/Single-precision_floating-point_format>`_ can be defined as:

    .. testcode::

        class IEEE754Single(Struct):
            fraction: 23
            exponent:  8 = 0x7f
            sign:      1

            def is_subnormal(self):
                return self.exponent == 0

    The ``IEEE754Single`` class itself can be used where a :ref:`shape <lang-shapes>` is expected:

    .. doctest::

        >>> IEEE754Single.as_shape()
        StructLayout({'fraction': 23, 'exponent': 8, 'sign': 1})
        >>> Signal(IEEE754Single).as_value().width
        32

    Instances of this class can be used where :ref:`values <lang-values>` are expected:

    .. doctest::

        >>> flt = Signal(IEEE754Single)
        >>> Signal(32).eq(flt)
        (eq (sig $signal) (sig flt))

    Accessing shape-castable properties returns slices of the underlying value:

    .. doctest::

        >>> flt.fraction
        (slice (sig flt) 0:23)
        >>> flt.is_subnormal()
        (== (slice (sig flt) 23:31) (const 1'd0))

    The reset values for individual fields can be overridden during instantiation:

    .. doctest::

        >>> hex(Signal(IEEE754Single).as_value().reset)
        '0x3f800000'
        >>> hex(Signal(IEEE754Single, reset={'sign': 1}).as_value().reset)
        '0xbf800000'
        >>> hex(Signal(IEEE754Single, reset={'exponent': 0}).as_value().reset)
        '0x0'

    Classes inheriting from :class:`Struct` can be used as base classes. The only restrictions
    are that:

    * Classes that do not define a layout cannot be instantiated or converted to a shape;
    * A layout can be defined exactly once in the inheritance hierarchy.

    Behavior can be shared through inheritance:

    .. testcode::

        class HasChecksum(Struct):
            def checksum(self):
                bits = Value.cast(self)
                return sum(bits[n:n+8] for n in range(0, len(bits), 8))

        class BareHeader(HasChecksum):
            address: 16
            length:   8

        class HeaderWithParam(HasChecksum):
            address: 16
            length:   8
            param:    8

    .. doctest::

        >>> HasChecksum.as_shape()
        Traceback (most recent call last):
          ...
        TypeError: Aggregate class 'HasChecksum' does not have a defined shape
        >>> bare = Signal(BareHeader); bare.checksum()
        (+ (+ (+ (const 1'd0) (slice (sig bare) 0:8)) (slice (sig bare) 8:16)) (slice (sig bare) 16:24))
        >>> param = Signal(HeaderWithParam); param.checksum()
        (+ (+ (+ (+ (const 1'd0) (slice (sig param) 0:8)) (slice (sig param) 8:16)) (slice (sig param) 16:24)) (slice (sig param) 24:32))
    """
    _AggregateMeta__layout_cls = StructLayout


class Union(View, metaclass=_AggregateMeta):
    """Unions defined with annotations.

    The :class:`Union` base class is a subclass of :class:`View` that provides a concise way
    to describe the union layout using Python :term:`variable annotations <python:variable
    annotation>`. It is very similar to the :class:`Struct` class, except that its layout
    is a :class:`UnionLayout`.

    A :class:`Union` can have only one field with a specified reset value. If a reset value is
    explicitly provided during instantiation, it overrides the reset value specified with
    an annotation:

    .. testcode::

        class VarInt(Union):
            int8:  8
            int16: 16 = 0x100

    .. doctest::

        >>> Signal(VarInt).as_value().reset
        256
        >>> Signal(VarInt, reset={'int8': 10}).as_value().reset
        10
    """
    _AggregateMeta__layout_cls = UnionLayout


# amaranth/lib/enum.py

import enum as py_enum
import warnings

from ..hdl.ast import Value, Shape, ShapeCastable, Const


__all__ = py_enum.__all__


for _member in py_enum.__all__:
    globals()[_member] = getattr(py_enum, _member)
del _member


class EnumMeta(ShapeCastable, py_enum.EnumMeta):
    """Subclass of the standard :class:`enum.EnumMeta` that implements the :class:`ShapeCastable`
    protocol.

    This metaclass provides the :meth:`as_shape` method, making its instances
    :ref:`shape-castable <lang-shapecasting>`, and accepts a ``shape=`` keyword argument
    to specify a shape explicitly. Other than this, it acts the same as the standard
    :class:`enum.EnumMeta` class; if the ``shape=`` argument is not specified and
    :meth:`as_shape` is never called, it places no restrictions on the enumeration class
    or the values of its members.
    """

    # TODO: remove this shim once py3.8 support is dropped
    @classmethod
    def __prepare__(metacls, name, bases, shape=None, **kwargs):
        return super().__prepare__(name, bases, **kwargs)

    def __new__(metacls, name, bases, namespace, shape=None, **kwargs):
        if shape is not None:
            shape = Shape.cast(shape)
        # Prepare enumeration members for instantiation. This logic is unfortunately very
        # convoluted because it supports two very different code paths that need to share
        # the emitted warnings.
        for member_name, member_value in namespace.items():
            if py_enum._is_sunder(member_name) or py_enum._is_dunder(member_name):
                continue
            # If a shape is specified ("Amaranth mode" of amaranth.lib.enum.Enum), then every
            # member value must be a constant-castable expression. Otherwise ("Python mode" of
            # amaranth.lib.enum.Enum) any value goes, since all enumerations accepted by
            # the built-in Enum class must be also accepted by amaranth.lib.enum.Enum.
            try:
                member_const = Const.cast(member_value)
            except TypeError as e:
                if shape is not None:
                    raise TypeError("Value {!r} of enumeration member {!r} must be "
                                    "a constant-castable expression"
                                    .format(member_value, member_name)) from e
                else:
                    continue
            if isinstance(member_value, Value):
                # The member value is an Amaranth value that is also constant-castable.
                # It cannot be used in an enumeration as-is (since it doesn't return a boolean
                # from comparison operators, and this is required by py_enum).
                # Replace the member value with the integer value of the constant, per RFC 4.
                # Note that we do this even if no shape is provided (and this class is emulating
                # a Python enumeration); this is OK because we only need to accept everything that
                # the built-in class accepts to be a drop-in replacement, but the built-in class
                # does not accept Amaranth values.
                # We use dict.__setitem__ since namespace is a py_enum._EnumDict that overrides
                # __setitem__ to check if the name has been already used.
                dict.__setitem__(namespace, member_name, member_const.value)
            # If a shape was specified, check whether the member value is compatible with it.
            if shape is not None:
                member_shape = member_const.shape()
                if member_shape.signed and not shape.signed:
                    warnings.warn(
                        message="Value {!r} of enumeration member {!r} is signed, but "
                                "the enumeration shape is {!r}" # the repr will be `unsigned(X)`
                                .format(member_value, member_name, shape),
                        category=SyntaxWarning,
                        stacklevel=2)
                elif (member_shape.width > shape.width or
                      member_shape.width == shape.width and
                        shape.signed and not member_shape.signed):
                    warnings.warn(
                        message="Value {!r} of enumeration member {!r} will be truncated to "
                                "the enumeration shape {!r}"
                                .format(member_value, member_name, shape),
                        category=SyntaxWarning,
                        stacklevel=2)
        # Actually instantiate the enumeration class.
        cls = py_enum.EnumMeta.__new__(metacls, name, bases, namespace, **kwargs)
        if shape is not None:
            # Shape is provided explicitly. Set the `_amaranth_shape_` attribute, and check that
            # the values of every member can be cast to the provided shape without truncation.
            cls._amaranth_shape_ = shape
        else:
            # Shape is not provided explicitly. Behave the same as a standard enumeration;
            # the lack of `_amaranth_shape_` attribute is used to emit a warning when such
            # an enumeration is used in a concatenation.
            pass
        return cls

    def as_shape(cls):
        """Cast this enumeration to a shape.

        Returns
        -------
        :class:`Shape`
            Explicitly provided shape. If not provided, returns the result of shape-casting
            this class :ref:`as a standard Python enumeration <lang-shapeenum>`.

        Raises
        ------
        TypeError
            If the enumeration has neither an explicitly provided shape nor any members.
        """
        if hasattr(cls, "_amaranth_shape_"):
            # Shape was provided explicitly; return it.
            return cls._amaranth_shape_
        else:
            # Shape was not provided explicitly; treat it the same way `Shape.cast` treats
            # standard library enumerations, so that `amaranth.lib.enum.Enum` can be a drop-in
            # replacement for `enum.Enum`.
            return Shape._cast_plain_enum(cls)

    def __call__(cls, value):
        # :class:`py_enum.Enum` uses ``__call__()`` for type casting: ``E(x)`` returns
        # the enumeration member whose value equals ``x``. In this case, ``x`` must be a concrete
        # value.
        # Amaranth extends this to indefinite values, but conceptually the operation is the same:
        # :class:`View` calls :meth:`Enum.__call__` to go from a :class:`Value` to something
        # representing this enumeration with that value.
        # At the moment however, for historical reasons, this is just the value itself. This works
        # and is backwards-compatible but is limiting in that it does not allow us to e.g. catch
        # comparisons with enum members of the wrong type.
        if isinstance(value, Value):
            return value
        return super().__call__(value)

    def const(cls, init):
        # Same considerations apply as above.
        if init is None:
            # Signal with unspecified reset value passes ``None`` to :meth:`const`.
            # Before RFC 9 was implemented, the unspecified reset value was 0, so this keeps
            # the old behavior intact.
            member = cls(0)
        else:
            member = cls(init)
        return Const(member.value, cls.as_shape())


class Enum(py_enum.Enum, metaclass=EnumMeta):
    """Subclass of the standard :class:`enum.Enum` that has :class:`EnumMeta` as
    its metaclass."""


class IntEnum(py_enum.IntEnum, metaclass=EnumMeta):
    """Subclass of the standard :class:`enum.IntEnum` that has :class:`EnumMeta` as
    its metaclass."""


class Flag(py_enum.Flag, metaclass=EnumMeta):
    """Subclass of the standard :class:`enum.Flag` that has :class:`EnumMeta` as
    its metaclass."""


class IntFlag(py_enum.IntFlag, metaclass=EnumMeta):
    """Subclass of the standard :class:`enum.IntFlag` that has :class:`EnumMeta` as
    its metaclass."""


# amaranth/lib/fifo.py

"""First-in first-out queues."""

from .. import *
from ..asserts import *
from .._utils import log2_int
from .coding import GrayEncoder, GrayDecoder
from .cdc import FFSynchronizer, AsyncFFSynchronizer


__all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncFIFOBuffered"]


class FIFOInterface:
    _doc_template = """
    {description}

    Parameters
    ----------
    width : int
        Bit width of data entries.
    depth : int
        Depth of the queue. If zero, the FIFO cannot be read from or written to.
    {parameters}

    Attributes
    ----------
    {attributes}
    w_data : Signal(width), in
        Input data.
    w_rdy : Signal(1), out
        Asserted if there is space in the queue, i.e. ``w_en`` can be asserted to write
        a new entry.
    w_en : Signal(1), in
        Write strobe. Latches ``w_data`` into the queue. Does nothing if ``w_rdy`` is not asserted.
    w_level : Signal(range(depth + 1)), out
        Number of unread entries.
    {w_attributes}
    r_data : Signal(width), out
        Output data. {r_data_valid}
    r_rdy : Signal(1), out
        Asserted if there is an entry in the queue, i.e. ``r_en`` can be asserted to read
        an existing entry.
    r_en : Signal(1), in
        Read strobe. Makes the next entry (if any) available on ``r_data`` at the next cycle.
        Does nothing if ``r_rdy`` is not asserted.
    r_level : Signal(range(depth + 1)), out
        Number of unread entries.
    {r_attributes}
    """

    __doc__ = _doc_template.format(description="""
    Data written to the input interface (``w_data``, ``w_rdy``, ``w_en``) is buffered and can be
    read at the output interface (``r_data``, ``r_rdy``, ``r_en``). The data entry written first
    to the input also appears first on the output.
    """,
    parameters="""
    fwft : bool
        First-word fallthrough. If set, when ``r_rdy`` rises, the first entry is already
        available, i.e. ``r_data`` is valid. Otherwise, after ``r_rdy`` rises, it is necessary
        to strobe ``r_en`` for ``r_data`` to become valid.
    """.strip(),
    r_data_valid="The conditions in which ``r_data`` is valid depends on the type of the queue.",
    attributes="",
    w_attributes="",
    r_attributes="")

    def __init__(self, *, width, depth, fwft):
        if not isinstance(width, int) or width < 0:
            raise TypeError("FIFO width must be a non-negative integer, not {!r}"
                            .format(width))
        if not isinstance(depth, int) or depth < 0:
            raise TypeError("FIFO depth must be a non-negative integer, not {!r}"
                            .format(depth))
        self.width = width
        self.depth = depth
        self.fwft  = fwft

        self.w_data = Signal(width, reset_less=True)
        self.w_rdy  = Signal() # writable; not full
        self.w_en   = Signal()
        self.w_level = Signal(range(depth + 1))

        self.r_data = Signal(width, reset_less=True)
        self.r_rdy  = Signal() # readable; not empty
        self.r_en   = Signal()
        self.r_level = Signal(range(depth + 1))


def _incr(signal, modulo):
    if modulo == 2 ** len(signal):
        return signal + 1
    else:
        return Mux(signal == modulo - 1, 0, signal + 1)


class SyncFIFO(Elaboratable, FIFOInterface):
    __doc__ = FIFOInterface._doc_template.format(
    description="""
    Synchronous first in, first out queue.

    Read and write interfaces are accessed from the same clock domain. If different clock domains
    are needed, use :class:`AsyncFIFO`.
    """.strip(),
    parameters="""
    fwft : bool
        First-word fallthrough. If set, when the queue is empty and an entry is written into it,
        that entry becomes available on the output on the same clock cycle. Otherwise, it is
        necessary to assert ``r_en`` for ``r_data`` to become valid.
    """.strip(),
    r_data_valid="For FWFT queues, valid if ``r_rdy`` is asserted. "
                 "For non-FWFT queues, valid on the next cycle after ``r_rdy`` and ``r_en`` have been asserted.",
    attributes="""
    level : Signal(range(depth + 1)), out
        Number of unread entries. This level is the same between read and write for synchronous FIFOs.
    """.strip(),
    r_attributes="",
    w_attributes="")

    def __init__(self, *, width, depth, fwft=True):
        super().__init__(width=width, depth=depth, fwft=fwft)

        self.level = Signal(range(depth + 1))

    def elaborate(self, platform):
        m = Module()
        if self.depth == 0:
            m.d.comb += [
                self.w_rdy.eq(0),
                self.r_rdy.eq(0),
            ]
            return m

        m.d.comb += [
            self.w_rdy.eq(self.level != self.depth),
            self.r_rdy.eq(self.level != 0),
            self.w_level.eq(self.level),
            self.r_level.eq(self.level),
        ]

        do_read  = self.r_rdy & self.r_en
        do_write = self.w_rdy & self.w_en

        storage = Memory(width=self.width, depth=self.depth)
        w_port  = m.submodules.w_port = storage.write_port()
        r_port  = m.submodules.r_port = storage.read_port(
            domain="comb" if self.fwft else "sync", transparent=self.fwft)
        produce = Signal(range(self.depth))
        consume = Signal(range(self.depth))

        m.d.comb += [
            w_port.addr.eq(produce),
            w_port.data.eq(self.w_data),
            w_port.en.eq(self.w_en & self.w_rdy),
        ]
        with m.If(do_write):
            m.d.sync += produce.eq(_incr(produce, self.depth))

        m.d.comb += [
            r_port.addr.eq(consume),
            self.r_data.eq(r_port.data),
        ]
        if not self.fwft:
            m.d.comb += r_port.en.eq(self.r_en)
        with m.If(do_read):
            m.d.sync += consume.eq(_incr(consume, self.depth))

        with m.If(do_write & ~do_read):
            m.d.sync += self.level.eq(self.level + 1)
        with m.If(do_read & ~do_write):
            m.d.sync += self.level.eq(self.level - 1)

        if platform == "formal":
            # TODO: move this logic to SymbiYosys
            with m.If(Initial()):
                m.d.comb += [
                    Assume(produce < self.depth),
                    Assume(consume < self.depth),
                ]
                with m.If(produce == consume):
                    m.d.comb += Assume((self.level == 0) | (self.level == self.depth))
                with m.If(produce > consume):
                    m.d.comb += Assume(self.level == (produce - consume))
                with m.If(produce < consume):
                    m.d.comb += Assume(self.level == (self.depth + produce - consume))
            with m.Else():
                m.d.comb += [
                    Assert(produce < self.depth),
                    Assert(consume < self.depth),
                ]
                with m.If(produce == consume):
                    m.d.comb += Assert((self.level == 0) | (self.level == self.depth))
                with m.If(produce > consume):
                    m.d.comb += Assert(self.level == (produce - consume))
                with m.If(produce < consume):
                    m.d.comb += Assert(self.level == (self.depth + produce - consume))

        return m


class SyncFIFOBuffered(Elaboratable, FIFOInterface):
    __doc__ = FIFOInterface._doc_template.format(
    description="""
    Buffered synchronous first in, first out queue.

    This queue's interface is identical to :class:`SyncFIFO` configured as ``fwft=True``, but it
    does not use asynchronous memory reads, which are incompatible with FPGA block RAMs.

    In exchange, the latency between an entry being written to an empty queue and that entry
    becoming available on the output is increased by one cycle compared to :class:`SyncFIFO`.
    """.strip(),
    parameters="""
    fwft : bool
        Always set.
    """.strip(),
    attributes="""
    level : Signal(range(depth + 1)), out
        Number of unread entries. This level is the same between read and write for synchronous FIFOs.
    """.strip(),
    r_data_valid="Valid if ``r_rdy`` is asserted.",
    r_attributes="",
    w_attributes="")

    def __init__(self, *, width, depth):
        super().__init__(width=width, depth=depth, fwft=True)

        self.level = Signal(range(depth + 1))

    def elaborate(self, platform):
        m = Module()
        if self.depth == 0:
            m.d.comb += [
                self.w_rdy.eq(0),
                self.r_rdy.eq(0),
            ]
            return m

        # Effectively, this queue treats the output register of the non-FWFT inner queue as
        # an additional storage element.
        m.submodules.unbuffered = fifo = SyncFIFO(width=self.width, depth=self.depth - 1,
                                                  fwft=False)

        m.d.comb += [
            fifo.w_data.eq(self.w_data),
            fifo.w_en.eq(self.w_en),
            self.w_rdy.eq(fifo.w_rdy),
        ]

        m.d.comb += [
            self.r_data.eq(fifo.r_data),
            fifo.r_en.eq(fifo.r_rdy & (~self.r_rdy | self.r_en)),
        ]
        with m.If(fifo.r_en):
            m.d.sync += self.r_rdy.eq(1)
        with m.Elif(self.r_en):
            m.d.sync += self.r_rdy.eq(0)

        m.d.comb += [
            self.level.eq(fifo.level + self.r_rdy),
            self.w_level.eq(self.level),
            self.r_level.eq(self.level),
        ]

        return m


class AsyncFIFO(Elaboratable, FIFOInterface):
    __doc__ = FIFOInterface._doc_template.format(
    description="""
    Asynchronous first in, first out queue.

    Read and write interfaces are accessed from different clock domains, which can be set when
    constructing the FIFO.

    :class:`AsyncFIFO` can be reset from the write clock domain. When the write domain reset is
    asserted, the FIFO becomes empty. When the read domain is reset, data remains in the FIFO - the
    read domain logic should correctly handle this case.

    :class:`AsyncFIFO` only supports power of 2 depths. Unless ``exact_depth`` is specified,
    the ``depth`` parameter is rounded up to the next power of 2.
    """.strip(),
    parameters="""
    r_domain : str
        Read clock domain.
    w_domain : str
        Write clock domain.
    fwft : bool
        Always set.
    """.strip(),
    attributes="",
    r_data_valid="Valid if ``r_rdy`` is asserted.",
    r_attributes="""
    r_rst : Signal(1), out
        Asserted, for at least one read-domain clock cycle, after the FIFO has been reset by
        the write-domain reset.
    """.strip(),
    w_attributes="")

    def __init__(self, *, width, depth, r_domain="read", w_domain="write", exact_depth=False):
        if depth != 0:
            try:
                depth_bits = log2_int(depth, need_pow2=exact_depth)
                depth = 1 << depth_bits
            except ValueError:
                raise ValueError("AsyncFIFO only supports depths that are powers of 2; requested "
                                 "exact depth {} is not"
                                 .format(depth)) from None
        else:
            depth_bits = 0
        super().__init__(width=width, depth=depth, fwft=True)

        self.r_rst = Signal()
        self._r_domain = r_domain
        self._w_domain = w_domain
        self._ctr_bits = depth_bits + 1

    def elaborate(self, platform):
        m = Module()
        if self.depth == 0:
            m.d.comb += [
                self.w_rdy.eq(0),
                self.r_rdy.eq(0),
            ]
            return m

        # The design of this queue is the "style #2" from Clifford E. Cummings' paper "Simulation
        # and Synthesis Techniques for Asynchronous FIFO Design":
        # http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf

        do_write = self.w_rdy & self.w_en
        do_read  = self.r_rdy & self.r_en

        # TODO: extract this pattern into lib.cdc.GrayCounter
        produce_w_bin = Signal(self._ctr_bits)
        produce_w_nxt = Signal(self._ctr_bits)
        m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write)
        m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt)

        # Note: Both read-domain counters must be reset_less (see comments below)
        consume_r_bin = Signal(self._ctr_bits, reset_less=True)
        consume_r_nxt = Signal(self._ctr_bits)
        m.d.comb += consume_r_nxt.eq(consume_r_bin + do_read)
        m.d[self._r_domain] += consume_r_bin.eq(consume_r_nxt)

        produce_w_gry = Signal(self._ctr_bits)
        produce_r_gry = Signal(self._ctr_bits)
        produce_enc = m.submodules.produce_enc = \
            GrayEncoder(self._ctr_bits)
        produce_cdc = m.submodules.produce_cdc = \
            FFSynchronizer(produce_w_gry, produce_r_gry, o_domain=self._r_domain)
        m.d.comb += produce_enc.i.eq(produce_w_nxt),
        m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o)

        consume_r_gry = Signal(self._ctr_bits, reset_less=True)
        consume_w_gry = Signal(self._ctr_bits)
        consume_enc = m.submodules.consume_enc = \
            GrayEncoder(self._ctr_bits)
        consume_cdc = m.submodules.consume_cdc = \
            FFSynchronizer(consume_r_gry, consume_w_gry, o_domain=self._w_domain)
        m.d.comb += consume_enc.i.eq(consume_r_nxt)
        m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o)

        consume_w_bin = Signal(self._ctr_bits)
        consume_dec = m.submodules.consume_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += consume_dec.i.eq(consume_w_gry),
        m.d[self._w_domain] += consume_w_bin.eq(consume_dec.o)

        produce_r_bin = Signal(self._ctr_bits)
        produce_dec = m.submodules.produce_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += produce_dec.i.eq(produce_r_gry),
        m.d.comb += produce_r_bin.eq(produce_dec.o)

        w_full  = Signal()
        r_empty = Signal()
        m.d.comb += [
            w_full.eq((produce_w_gry[-1]  != consume_w_gry[-1]) &
                      (produce_w_gry[-2]  != consume_w_gry[-2]) &
                      (produce_w_gry[:-2] == consume_w_gry[:-2])),
            r_empty.eq(consume_r_gry == produce_r_gry),
        ]

        m.d[self._w_domain] += self.w_level.eq((produce_w_bin - consume_w_bin))
        m.d.comb += self.r_level.eq((produce_r_bin - consume_r_bin))

        storage = Memory(width=self.width, depth=self.depth)
        w_port  = m.submodules.w_port = storage.write_port(domain=self._w_domain)
        r_port  = m.submodules.r_port = storage.read_port (domain=self._r_domain,
                                                           transparent=False)
        m.d.comb += [
            w_port.addr.eq(produce_w_bin[:-1]),
            w_port.data.eq(self.w_data),
            w_port.en.eq(do_write),
            self.w_rdy.eq(~w_full),
        ]
        m.d.comb += [
            r_port.addr.eq(consume_r_nxt[:-1]),
            self.r_data.eq(r_port.data),
            r_port.en.eq(1),
            self.r_rdy.eq(~r_empty),
        ]

        # Reset handling to maintain FIFO and CDC invariants in the presence of a write-domain
        # reset.
        # There is a CDC hazard associated with resetting an async FIFO - Gray code counters which
        # are reset to 0 violate their Gray code invariant. One way to handle this is to ensure
        # that both sides of the FIFO are asynchronously reset by the same signal. We adopt a
        # slight variation on this approach - reset control rests entirely with the write domain.
        # The write domain's reset signal is used to asynchronously reset the read domain's
        # counters and force the FIFO to be empty when the write domain's reset is asserted.
        # This requires the two read domain counters to be marked as "reset_less", as they are
        # reset through another mechanism. See https://github.com/amaranth-lang/amaranth/issues/181
        # for the full discussion.
        w_rst = ResetSignal(domain=self._w_domain, allow_reset_less=True)
        r_rst = Signal()

        # Async-set-sync-release synchronizer avoids CDC hazards
        rst_cdc = m.submodules.rst_cdc = \
            AsyncFFSynchronizer(w_rst, r_rst, o_domain=self._r_domain)

        # Decode Gray code counter synchronized from write domain to overwrite binary
        # counter in read domain.
        rst_dec = m.submodules.rst_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += rst_dec.i.eq(produce_r_gry)
        with m.If(r_rst):
            m.d.comb += r_empty.eq(1)
            m.d[self._r_domain] += consume_r_gry.eq(produce_r_gry)
            m.d[self._r_domain] += consume_r_bin.eq(rst_dec.o)
            m.d[self._r_domain] += self.r_rst.eq(1)
        with m.Else():
            m.d[self._r_domain] += self.r_rst.eq(0)

        if platform == "formal":
            with m.If(Initial()):
                m.d.comb += Assume(produce_w_gry == (produce_w_bin ^ produce_w_bin[1:]))
                m.d.comb += Assume(consume_r_gry == (consume_r_bin ^ consume_r_bin[1:]))

        return m


class AsyncFIFOBuffered(Elaboratable, FIFOInterface):
    __doc__ = FIFOInterface._doc_template.format(
    description="""
    Buffered asynchronous first in, first out queue.

    Read and write interfaces are accessed from different clock domains, which can be set when
    constructing the FIFO.

    :class:`AsyncFIFOBuffered` only supports power of 2 plus one depths. Unless ``exact_depth``
    is specified, the ``depth`` parameter is rounded up to the next power of 2 plus one.
    (The output buffer acts as an additional queue element.)

    This queue's interface is identical to :class:`AsyncFIFO`, but it has an additional register
    on the output, improving timing in case of block RAM that has large clock-to-output delay.

    In exchange, the latency between an entry being written to an empty queue and that entry
    becoming available on the output is increased by one cycle compared to :class:`AsyncFIFO`.
    """.strip(),
    parameters="""
    r_domain : str
        Read clock domain.
    w_domain : str
        Write clock domain.
    fwft : bool
        Always set.
    """.strip(),
    attributes="",
    r_data_valid="Valid if ``r_rdy`` is asserted.",
    r_attributes="""
    r_rst : Signal(1), out
        Asserted, for at least one read-domain clock cycle, after the FIFO has been reset by
        the write-domain reset.
    """.strip(),
    w_attributes="")

    def __init__(self, *, width, depth, r_domain="read", w_domain="write", exact_depth=False):
        if depth != 0:
            try:
                depth_bits = log2_int(max(0, depth - 1), need_pow2=exact_depth)
                depth = (1 << depth_bits) + 1
            except ValueError:
                raise ValueError("AsyncFIFOBuffered only supports depths that are one higher "
                                 "than powers of 2; requested exact depth {} is not"
                                 .format(depth)) from None
        super().__init__(width=width, depth=depth, fwft=True)

        self.r_rst = Signal()
        self._r_domain = r_domain
        self._w_domain = w_domain

    def elaborate(self, platform):
        m = Module()
        if self.depth == 0:
            m.d.comb += [
                self.w_rdy.eq(0),
                self.r_rdy.eq(0),
            ]
            return m

        m.submodules.unbuffered = fifo = AsyncFIFO(width=self.width, depth=self.depth - 1,
            r_domain=self._r_domain, w_domain=self._w_domain)

        m.d.comb += [
            fifo.w_data.eq(self.w_data),
            self.w_rdy.eq(fifo.w_rdy),
            fifo.w_en.eq(self.w_en),
        ]

        r_consume_buffered = Signal()
        m.d.comb += r_consume_buffered.eq((self.r_rdy - self.r_en) & self.r_rdy)
        m.d[self._r_domain] += self.r_level.eq(fifo.r_level + r_consume_buffered)

        w_consume_buffered = Signal()
        m.submodules.consume_buffered_cdc = FFSynchronizer(r_consume_buffered, w_consume_buffered, o_domain=self._w_domain, stages=4)
        m.d.comb += self.w_level.eq(fifo.w_level + w_consume_buffered)

        with m.If(self.r_en | ~self.r_rdy):
            m.d[self._r_domain] += [
                self.r_data.eq(fifo.r_data),
                self.r_rdy.eq(fifo.r_rdy),
                self.r_rst.eq(fifo.r_rst),
            ]
            m.d.comb += [
                fifo.r_en.eq(1)
            ]

        return m


# amaranth/lib/io.py

from .. import *
from ..hdl.rec import *


__all__ = ["pin_layout", "Pin"]


def pin_layout(width, dir, xdr=0):
    """
    Layout of the platform interface of a pin or several pins, which may be used inside
    user-defined records.

    See :class:`Pin` for details.
    """
    if not isinstance(width, int) or width < 0:
        raise TypeError("Width must be a non-negative integer, not {!r}"
                        .format(width))
    if dir not in ("i", "o", "oe", "io"):
        raise TypeError("Direction must be one of \"i\", \"o\", \"io\", or \"oe\", not {!r}"""
                        .format(dir))
    if not isinstance(xdr, int) or xdr < 0:
        raise TypeError("Gearing ratio must be a non-negative integer, not {!r}"
                        .format(xdr))

    fields = []
    if dir in ("i", "io"):
        if xdr > 0:
            fields.append(("i_clk", 1))
        if xdr > 2:
            fields.append(("i_fclk", 1))
        if xdr in (0, 1):
            fields.append(("i", width))
        else:
            for n in range(xdr):
                fields.append(("i{}".format(n), width))
    if dir in ("o", "oe", "io"):
        if xdr > 0:
            fields.append(("o_clk", 1))
        if xdr > 2:
            fields.append(("o_fclk", 1))
        if xdr in (0, 1):
            fields.append(("o", width))
        else:
            for n in range(xdr):
                fields.append(("o{}".format(n), width))
    if dir in ("oe", "io"):
        fields.append(("oe", 1))
    return Layout(fields)


class Pin(Record):
    """
    An interface to an I/O buffer or a group of them that provides uniform access to input, output,
    or tristate buffers that may include a 1:n gearbox. (A 1:2 gearbox is typically called "DDR".)

    A :class:`Pin` is identical to a :class:`Record` that uses the corresponding :meth:`pin_layout`
    except that it allows accessing the parameters like ``width`` as attributes. It is legal to use
    a plain :class:`Record` anywhere a :class:`Pin` is used, provided that these attributes are
    not necessary.

    Parameters
    ----------
    width : int
        Width of the ``i``/``iN`` and ``o``/``oN`` signals.
    dir : ``"i"``, ``"o"``, ``"io"``, ``"oe"``
        Direction of the buffers. If ``"i"`` is specified, only the ``i``/``iN`` signals are
        present. If ``"o"`` is specified, only the ``o``/``oN`` signals are present. If ``"oe"`` is
        specified, the ``o``/``oN`` signals are present, and an ``oe`` signal is present.
        If ``"io"`` is specified, both the ``i``/``iN`` and ``o``/``oN`` signals are present, and
        an ``oe`` signal is present.
    xdr : int
        Gearbox ratio. If equal to 0, the I/O buffer is combinatorial, and only ``i``/``o``
        signals are present. If equal to 1, the I/O buffer is SDR, and only ``i``/``o`` signals are
        present. If greater than 1, the I/O buffer includes a gearbox, and ``iN``/``oN`` signals
        are present instead, where ``N in range(0, N)``. For example, if ``xdr=2``, the I/O buffer
        is DDR; the signal ``i0`` reflects the value at the rising edge, and the signal ``i1``
        reflects the value at the falling edge.
    name : str
        Name of the underlying record.

    Attributes
    ----------
    i_clk:
        I/O buffer input clock. Synchronizes `i*`. Present if ``xdr`` is nonzero.
    i_fclk:
        I/O buffer input fast clock. Synchronizes `i*` on higher gearbox ratios. Present if ``xdr``
        is greater than 2.
    i : Signal, out
        I/O buffer input, without gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is
        equal to 0 or 1.
    i0, i1, ... : Signal, out
        I/O buffer inputs, with gearing. Present if ``dir="i"`` or ``dir="io"``, and ``xdr`` is
        greater than 1.
    o_clk:
        I/O buffer output clock. Synchronizes `o*`, including `oe`. Present if ``xdr`` is nonzero.
    o_fclk:
        I/O buffer output fast clock. Synchronizes `o*` on higher gearbox ratios. Present if
        ``xdr`` is greater than 2.
    o : Signal, in
        I/O buffer output, without gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is
        equal to 0 or 1.
    o0, o1, ... : Signal, in
        I/O buffer outputs, with gearing. Present if ``dir="o"`` or ``dir="io"``, and ``xdr`` is
        greater than 1.
    oe : Signal, in
        I/O buffer output enable. Present if ``dir="io"`` or ``dir="oe"``. Buffers generally
        cannot change direction more than once per cycle, so at most one output enable signal
        is present.
    """
    def __init__(self, width, dir, *, xdr=0, name=None, src_loc_at=0):
        self.width = width
        self.dir   = dir
        self.xdr   = xdr

        super().__init__(pin_layout(self.width, self.dir, self.xdr),
                         name=name, src_loc_at=src_loc_at + 1)


# amaranth/lib/scheduler.py

from .. import *


__all__ = ["RoundRobin"]


class RoundRobin(Elaboratable):
    """Round-robin scheduler.

    For a given set of requests, the round-robin scheduler will
    grant one request. Once it grants a request, if any other
    requests are active, it grants the next active request with
    a greater number, restarting from zero once it reaches the
    highest one.

    Use :class:`EnableInserter` to control when the scheduler
    is updated.

    Parameters
    ----------
    count : int
        Number of requests.

    Attributes
    ----------
    requests : Signal(count), in
        Set of requests.
    grant : Signal(range(count)), out
        Number of the granted request. Does not change if there are no
        active requests.
    valid : Signal(), out
        Asserted if grant corresponds to an active request. Deasserted
        otherwise, i.e. if no requests are active.
    """
    def __init__(self, *, count):
        if not isinstance(count, int) or count < 0:
            raise ValueError("Count must be a non-negative integer, not {!r}"
                             .format(count))
        self.count    = count

        self.requests = Signal(count)
        self.grant    = Signal(range(count))
        self.valid    = Signal()

    def elaborate(self, platform):
        m = Module()

        with m.Switch(self.grant):
            for i in range(self.count):
                with m.Case(i):
                    for pred in reversed(range(i)):
                        with m.If(self.requests[pred]):
                            m.d.sync += self.grant.eq(pred)
                    for succ in reversed(range(i + 1, self.count)):
                        with m.If(self.requests[succ]):
                            m.d.sync += self.grant.eq(succ)

        m.d.sync += self.valid.eq(self.requests.any())

        return m


# amaranth/lib/wiring.py

from collections.abc import Mapping
import enum
import types
import inspect
import re
import warnings

from ..hdl.ast import Shape, ShapeCastable, Const, Signal, Value, ValueCastable
from ..hdl.ir import Elaboratable
from .._utils import final


__all__ = ["In", "Out", "Signature", "connect", "flipped", "Component"]


class Flow(enum.Enum):
    Out = 0
    In = 1

    def flip(self):
        if self == Out:
            return In
        if self == In:
            return Out
        assert False # :nocov:

    def __call__(self, description, *, reset=None):
        return Member(self, description, reset=reset)

    def __repr__(self):
        return self.name

    def __str__(self):
        return self.name


In = Flow.In
Out = Flow.Out


@final
class Member:
    def __init__(self, flow, description, *, reset=None, _dimensions=()):
        self._flow = flow
        self._description = description
        self._reset = reset
        self._dimensions = _dimensions

        # Check that the description is valid, and populate derived properties.
        if self.is_port:
            # Cast the description to a shape for typechecking, but keep the original
            # shape-castable so that it can be provided
            try:
                shape = Shape.cast(self._description)
            except TypeError as e:
                raise TypeError(f"Port member description must be a shape-castable object or "
                                f"a signature, not {description!r}") from e
            # This mirrors the logic that handles Signal(reset=).
            # TODO: We need a simpler way to check for "is this a valid constant initializer"
            if issubclass(type(self._description), ShapeCastable):
                try:
                    self._reset_as_const = Const.cast(self._description.const(self._reset))
                except Exception as e:
                    raise TypeError(f"Port member reset value {self._reset!r} is not a valid "
                                    f"constant initializer for {self._description}") from e
            else:
                try:
                    self._reset_as_const = Const.cast(reset or 0)
                except TypeError:
                    raise TypeError(f"Port member reset value {self._reset!r} is not a valid "
                                    f"constant initializer for {shape}")
        if self.is_signature:
            if self._reset is not None:
                raise ValueError(f"A signature member cannot have a reset value")

    def flip(self):
        return Member(self._flow.flip(), self._description, reset=self._reset,
                      _dimensions=self._dimensions)

    def array(self, *dimensions):
        for dimension in dimensions:
            if not (isinstance(dimension, int) and dimension >= 0):
                raise TypeError(f"Member array dimensions must be non-negative integers, "
                                f"not {dimension!r}")
        return Member(self._flow, self._description, reset=self._reset,
                      _dimensions=(*dimensions, *self._dimensions))

    @property
    def flow(self):
        return self._flow

    @property
    def is_port(self):
        return not isinstance(self._description, Signature)

    @property
    def is_signature(self):
        return isinstance(self._description, Signature)

    @property
    def shape(self):
        if self.is_signature:
            raise AttributeError(f"A signature member does not have a shape")
        return self._description

    @property
    def reset(self):
        if self.is_signature:
            raise AttributeError(f"A signature member does not have a reset value")
        return self._reset

    @property
    def signature(self):
        if self.is_port:
            raise AttributeError(f"A port member does not have a signature")
        if self.flow == Out:
            return self._description
        if self.flow == In:
            return self._description.flip()
        assert False # :nocov:

    @property
    def dimensions(self):
        return self._dimensions

    def __eq__(self, other):
        return (type(other) is Member and
                self._flow == other._flow and
                self._description == other._description and
                self._reset == other._reset and
                self._dimensions == other._dimensions)

    def __repr__(self):
        reset_repr = dimensions_repr = ""
        if self._reset:
            reset_repr = f", reset={self._reset!r}"
        if self._dimensions:
            dimensions_repr = f".array({', '.join(map(str, self._dimensions))})"
        return f"{self._flow!r}({self._description!r}{reset_repr}){dimensions_repr}"


@final
class SignatureError(Exception):
    pass


# Inherits from Mapping and not MutableMapping because it's only mutable in a very limited way
# and most of the methods (except for `update`) added by MutableMapping are useless.
@final
class SignatureMembers(Mapping):
    def __init__(self, members=()):
        self._dict = dict()
        self._frozen = False
        self += members

    def flip(self):
        return FlippedSignatureMembers(self)

    def __eq__(self, other):
        return (isinstance(other, (SignatureMembers, FlippedSignatureMembers)) and
                list(self.flatten()) == list(other.flatten()))

    def __contains__(self, name):
        return name in self._dict

    def _check_name(self, name):
        if not isinstance(name, str):
            raise TypeError(f"Member name must be a string, not {name!r}")
        if not re.match(r"^[A-Za-z][0-9A-Za-z_]*$", name):
            raise NameError(f"Member name '{name}' must be a valid, public Python attribute name")
        if name == "signature":
            raise NameError(f"Member name cannot be '{name}'")

    def __getitem__(self, name):
        self._check_name(name)
        if name not in self._dict:
            raise SignatureError(f"Member '{name}' is not a part of the signature")
        return self._dict[name]

    def __setitem__(self, name, member):
        self._check_name(name)
        if name in self._dict:
            raise SignatureError(f"Member '{name}' already exists in the signature and cannot "
                                 f"be replaced")
        if type(member) is not Member:
            raise TypeError(f"Assigned value {member!r} must be a member; "
                            f"did you mean In({member!r}) or Out({member!r})?")
        if self._frozen:
            raise SignatureError("Cannot add members to a frozen signature")
        self._dict[name] = member

    def __delitem__(self, name):
        raise SignatureError("Members cannot be removed from a signature")

    def __iter__(self):
        return iter(sorted(self._dict))

    def __len__(self):
        return len(self._dict)

    def __iadd__(self, members):
        for name, member in dict(members).items():
            self[name] = member
        return self

    @property
    def frozen(self):
        return self._frozen

    def freeze(self):
        self._frozen = True
        for member in self.values():
            if member.is_signature:
                member.signature.freeze()

    def flatten(self, *, path=()):
        for name, member in self.items():
            yield ((*path, name), member)
            if member.is_signature:
                yield from member.signature.members.flatten(path=(*path, name))

    def create(self, *, path=()):
        attrs = {}
        for name, member in self.items():
            def create_value(path):
                if member.is_port:
                    return Signal(member.shape, reset=member.reset,
                                  name="__".join(str(item) for item in path))
                if member.is_signature:
                    return member.signature.create(path=path)
                assert False # :nocov:
            def create_dimensions(dimensions, *, path):
                if not dimensions:
                    return create_value(path)
                dimension, *rest_of_dimensions = dimensions
                return [create_dimensions(rest_of_dimensions, path=(*path, index))
                        for index in range(dimension)]
            attrs[name] = create_dimensions(member.dimensions, path=(*path, name))
        return attrs

    def __repr__(self):
        frozen_repr = ".freeze()" if self._frozen else ""
        return f"SignatureMembers({self._dict}){frozen_repr}"


@final
class FlippedSignatureMembers(Mapping):
    def __init__(self, unflipped):
        self.__unflipped = unflipped

    def flip(self):
        return self.__unflipped

    # See the note below.
    __eq__ = SignatureMembers.__eq__

    def __contains__(self, name):
        return name in self.__unflipped

    def __getitem__(self, name):
        return self.__unflipped.__getitem__(name).flip()

    def __setitem__(self, name, member):
        self.__unflipped.__setitem__(name, member.flip())

    def __delitem__(self, name):
        self.__unflipped.__delitem__(name)

    def __iter__(self):
        return self.__unflipped.__iter__()

    def __len__(self):
        return self.__unflipped.__len__()

    def __iadd__(self, members):
        self.__unflipped.__iadd__({name: member.flip() for name, member in members.items()})
        return self

    @property
    def frozen(self):
        return self.__unflipped.frozen

    def freeze(self):
        self.__unflipped.freeze()

    # These methods do not access instance variables and so their implementation can be shared
    # between the normal and the flipped member collections.
    flatten = SignatureMembers.flatten
    create = SignatureMembers.create

    def __repr__(self):
        return f"{self.__unflipped!r}.flip()"


def _format_path(path):
    first, *rest = path
    if isinstance(first, int):
        # only happens in connect()
        chunks = [f"arg{first}"]
    else:
        chunks = [first]
    for item in rest:
        if isinstance(item, int):
            chunks.append(f"[{item}]")
        else:
            chunks.append(f".{item}")
    return f"'{''.join(chunks)}'"


def _traverse_path(path, obj):
    first, *rest = path
    obj = obj[first]
    for item in rest:
        if isinstance(item, int):
            obj = obj[item]
        else:
            obj = getattr(obj, item)
    return obj


def _format_shape(shape):
    if type(shape) is Shape:
        return f"{shape}"
    if isinstance(shape, int):
        return f"{Shape.cast(shape)}"
    return f"{Shape.cast(shape)} ({shape!r})"


class SignatureMeta(type):
    def __subclasscheck__(cls, subclass):
        # `FlippedSignature` is a subclass of `Signature` or any of its subclasses because all of
        # them may return a Liskov-compatible instance of it from `self.flip()`.
        if subclass is FlippedSignature:
            return True
        return super().__subclasscheck__(subclass)

    def __instancecheck__(cls, instance):
        # `FlippedSignature` is an instance of a `Signature` or its subclass if the unflipped
        # object is.
        if type(instance) is FlippedSignature:
            return super().__instancecheck__(instance.flip())
        return super().__instancecheck__(instance)


class Signature(metaclass=SignatureMeta):
    def __init__(self, members):
        self.__members = SignatureMembers(members)

    def flip(self):
        return FlippedSignature(self)

    @property
    def members(self):
        return self.__members

    def __eq__(self, other):
        other_unflipped = other.flip() if type(other) is FlippedSignature else other
        if type(self) is type(other_unflipped) is Signature:
            # If both `self` and `other` are anonymous signatures, compare structurally.
            return self.members == other.members
        else:
            # Otherwise (if `self` refers to a derived class) compare by identity. This will
            # usually be overridden in a derived class.
            return self is other

    @property
    def frozen(self):
        return self.members.frozen

    def freeze(self):
        self.members.freeze()
        return self

    def is_compliant(self, obj, *, reasons=None, path=("obj",)):
        def check_attr_value(member, attr_value, *, path):
            if member.is_port:
                try:
                    attr_value_cast = Value.cast(attr_value)
                except:
                    if reasons is not None:
                        reasons.append(f"{_format_path(path)} is not a value-castable object, "
                                       f"but {attr_value!r}")
                    return False
                if not isinstance(attr_value_cast, (Signal, Const)):
                    if reasons is not None:
                        reasons.append(f"{_format_path(path)} is neither a signal nor a constant, "
                                       f"but {attr_value_cast!r}")
                    return False
                attr_shape = attr_value_cast.shape()
                if Shape.cast(attr_shape) != Shape.cast(member.shape):
                    if reasons is not None:
                        reasons.append(f"{_format_path(path)} is expected to have "
                                       f"the shape {_format_shape(member.shape)}, but it has "
                                       f"the shape {_format_shape(attr_shape)}")
                    return False
                if isinstance(attr_value_cast, Signal):
                    if attr_value_cast.reset != member._reset_as_const.value:
                        if reasons is not None:
                            reasons.append(f"{_format_path(path)} is expected to have "
                                           f"the reset value {member.reset!r}, but it has "
                                           f"the reset value {attr_value_cast.reset!r}")
                        return False
                    if attr_value_cast.reset_less:
                        if reasons is not None:
                            reasons.append(f"{_format_path(path)} is expected to not be reset-less")
                        return False
                return True
            if member.is_signature:
                return member.signature.is_compliant(attr_value, reasons=reasons, path=path)
            assert False # :nocov:

        def check_dimensions(member, attr_value, dimensions, *, path):
            if not dimensions:
                return check_attr_value(member, attr_value, path=path)

            dimension, *rest_of_dimensions = dimensions
            if not isinstance(attr_value, (tuple, list)):
                if reasons is not None:
                    reasons.append(f"{_format_path(path)} is expected to be a tuple or a list, "
                                   f"but it is a {attr_value!r}")
                return False
            if len(attr_value) != dimension:
                if reasons is not None:
                    reasons.append(f"{_format_path(path)} is expected to have dimension "
                                   f"{dimension}, but its length is {len(attr_value)}")
                return False

            result = True
            for index in range(dimension):
                if not check_dimensions(member, attr_value[index], rest_of_dimensions,
                                        path=(*path, index)):
                    result = False
                    if reasons is None:
                        break # short cicruit if detailed error message isn't required
            return result

        result = True
        for attr_name, member in self.members.items():
            try:
                attr_value = getattr(obj, attr_name)
            except AttributeError:
                if reasons is None:
                    return False
                else:
                    reasons.append(f"{_format_path(path)} does not have an attribute "
                                   f"{attr_name!r}")
                    result = False
                    continue
            if not check_dimensions(member, attr_value, member.dimensions, path=(*path, attr_name)):
                if reasons is None:
                    return False
                else:
                    # `reasons` was mutated by check_dimensions()
                    result = False
                    continue
        return result

    def create(self, *, path=()):
        return Interface(self, path=path)

    def __repr__(self):
        if type(self) is Signature:
            return f"Signature({dict(self.members.items())})"
        return super().__repr__()


# To simplify implementation and reduce API surface area `FlippedSignature` is made final. This
# restriction could be lifted if there is a compelling use case.
@final
class FlippedSignature:
    def __init__(self, signature):
        object.__setattr__(self, "_FlippedSignature__unflipped", signature)

    def flip(self):
        return self.__unflipped

    @property
    def members(self):
        return FlippedSignatureMembers(self.__unflipped.members)

    def __eq__(self, other):
        if type(other) is FlippedSignature:
            # Trivial case.
            return self.flip() == other.flip()
        else:
            # Delegate comparisons back to Signature (or its descendant) by flipping the arguments;
            # equality must be reflexive but the implementation of `__eq__` need not be, and we can
            # take advantage of it here. This is done by returning `NotImplemented`, otherwise if
            # the other object cannot be compared to a `FlippedSignature` either this will result
            # in infinite recursion.
            return NotImplemented

    # These methods do not access instance variables and so their implementation can be shared
    # between the normal and the flipped member collections.
    frozen = Signature.frozen
    freeze = Signature.freeze
    is_compliant = Signature.is_compliant
    create = Signature.create

    # FIXME: document this logic
    def __getattr__(self, name):
        value = getattr(self.__unflipped, name)
        if inspect.ismethod(value):
            return types.MethodType(value.__func__, self)
        else:
            return value

    def __setattr__(self, name, value):
        return setattr(self.__unflipped, name, value)

    def __repr__(self):
        return f"{self.__unflipped!r}.flip()"


class Interface:
    def __init__(self, signature, *, path):
        self.__dict__.update({
            "signature": signature,
            **signature.members.create(path=path)
        })


# To reduce API surface area `FlippedInterface` is made final. This restriction could be lifted
# if there is a compelling use case.
@final
class FlippedInterface:
    def __init__(self, interface):
        if not (hasattr(interface, "signature") and isinstance(interface.signature, Signature)):
            raise TypeError(f"flipped() can only flip an interface object, not {interface!r}")
        object.__setattr__(self, "_FlippedInterface__unflipped", interface)

    @property
    def signature(self):
        return self.__unflipped.signature.flip()

    def __eq__(self, other):
        return type(self) is type(other) and self.__unflipped == other.__unflipped

    # FIXME: document this logic
    def __getattr__(self, name):
        value = getattr(self.__unflipped, name)
        if inspect.ismethod(value):
            return types.MethodType(value.__func__, self)
        else:
            return value

    def __setattr__(self, name, value):
        return setattr(self.__unflipped, name, value)

    def __repr__(self):
        return f"flipped({self.__unflipped!r})"


def flipped(interface):
    return FlippedInterface(interface)


@final
class ConnectionError(Exception):
    pass


def connect(m, *args, **kwargs):
    objects = {
        **{index:   arg for index,   arg in enumerate(args)},
        **{keyword: arg for keyword, arg in kwargs.items()}
    }

    # Extract signatures from arguments.
    signatures = {}
    for handle, obj in objects.items():
        if not hasattr(obj, "signature"):
            raise AttributeError(f"Argument {handle!r} must have a 'signature' attribute")
        if not isinstance(obj.signature, Signature):
            raise TypeError(f"Signature of argument {handle!r} must be a signature, "
                            f"not {obj.signature!r}")
        if not obj.signature.is_compliant(obj):
            reasons = []
            obj.signature.is_compliant(obj, reasons=reasons, path=(handle,))
            reasons_as_string = "".join("\n- " + reason for reason in reasons)
            raise ConnectionError(f"Argument {handle!r} does not match its signature:" +
                                  reasons_as_string)
        signatures[handle] = obj.signature.freeze()

    # Collate signatures and build connections.
    flattens = {handle: signature.members.flatten()
                for handle, signature in signatures.items()}
    connections = []
    # Each iteration of the outer loop is intended to connect several (usually a pair) members
    # to each other, e.g. an out member `[0].a` to an in member `[1].a`. However, because we
    # do not just check signatures for equality (in order to improve diagnostics), it is possible
    # that we will find that in `[0]`, the first member is `a`, and in `[1]`, the first member
    # is completely unrelated `[b]`. Since the assumption that all signatures are equal, or even
    # of equal length, cannot be made, it is necessary to simultaneously iterate (like with `zip`)
    # the signature of every object being connected, making sure each set of next members is
    # is_compliant with each other.
    while True:
        # Classify the members by kind and flow: signature, In, Out. Flow of signature members is
        # implied in the flow of each port member, so the signature members are only classified
        # here to ensure they are not connected to port members.
        is_first = True
        sig_kind, out_kind, in_kind = [], [], []
        for handle, flattened_members in flattens.items():
            path_for_handle, member = next(flattened_members, (None, None))
            # First, ensure that the paths are equal (i.e. that the hierarchy matches for all of
            # the objects up to this point).
            if is_first:
                is_first = False
                first_path = path_for_handle
            else:
                first_handle = next(iter(flattens))
                if first_path != path_for_handle:
                    # The paths are inequal. It is ambiguous how exactly the diagnostic should be
                    # displayed, and the choices of which other member to use below is arbitrary.
                    # Signature members are iterated in ascending lexicographical order, so the path
                    # that sorts greater corresponds to the handle that's missing a member.
                    if (path_for_handle is None or
                            (first_path is not None and path_for_handle > first_path)):
                        first_path_as_string = _format_path(first_path)
                        raise ConnectionError(f"Member {first_path_as_string} is present in "
                                              f"{first_handle!r}, but not in {handle!r}")
                    if (first_path is None or
                            (path_for_handle is not None and path_for_handle < first_path)):
                        path_for_handle_as_string = _format_path(path_for_handle)
                        raise ConnectionError(f"Member {path_for_handle_as_string} is present in "
                                              f"{handle!r}, but not in {first_handle!r}")
                    assert False # :nocov:
            # If there is no actual member, the signature has been fully iterated through.
            # Other signatures may still have extraneous members, so continue iterating until
            # a diagnostic is returned.
            if member is None:
                continue
            # At this point we know the paths are equal, but the members can still have
            # inis_compliant flow, kind (signature or port), signature, or shape. Collect all of
            # these for later evaluation.
            if member.is_port:
                if member.flow == Out:
                    out_kind.append(((handle, *path_for_handle), member))
                if member.flow == In:
                    in_kind.append(((handle, *path_for_handle), member))
            if member.is_signature:
                sig_kind.append(((handle, *path_for_handle), member))
        # If there's no path and an error wasn't raised above, we're done!
        if first_path is None:
            break
        # At this point, valid possibilities are:
        # - All of the members are signature members. In this case, we move on to their contents,
        #   and ignore the signatures themselves.
        # - There are no signature members, and there is exactly one Out flow member. In this case,
        #   this member is connected to the remaining In members, of which there may be any amount.
        # All other cases must be rejected with a diagnostic.
        if sig_kind and (out_kind or in_kind):
            sig_member_paths_as_string = \
                ", ".join(_format_path(h) for h, m in sig_kind)
            port_member_paths_as_string = \
                ", ".join(_format_path(h) for h, m in out_kind + in_kind)
            raise ConnectionError(
                f"Cannot connect signature member(s) {sig_member_paths_as_string} with "
                f"port member(s) {port_member_paths_as_string}")
        if sig_kind:
            # There are no port members at this point; we're done with this path.
            continue
        # There are only port members after this point.
        is_first = True
        for (path, member) in in_kind + out_kind:
            member_shape = member.shape
            if is_first:
                is_first = False
                first_path = path
                first_member_shape = member.shape
                first_member_reset = member.reset
                first_member_reset_as_const = member._reset_as_const
                continue
            if Shape.cast(first_member_shape).width != Shape.cast(member_shape).width:
                raise ConnectionError(
                    f"Cannot connect the member {_format_path(first_path)} with shape "
                    f"{_format_shape(first_member_shape)} to the member {_format_path(path)} with "
                    f"shape {_format_shape(member_shape)} because the shape widths "
                    f"({Shape.cast(first_member_shape).width} and "
                    f"{Shape.cast(member_shape).width}) do not match")
            if first_member_reset_as_const.value != member._reset_as_const.value:
                raise ConnectionError(
                    f"Cannot connect together the member {_format_path(first_path)} with reset "
                    f"value {first_member_reset!r} and the member {_format_path(path)} with reset "
                    f"value {member.reset} because the reset values do not match")
        # If there are no Out members, there is nothing to connect. The In members, while not
        # explicitly connected, will stay at the same value since we ensured their reset values
        # are all identical.
        if len(out_kind) == 0:
            continue
        # Check that there is only one Out member. In the future we could extend connection to
        # handle wired-OR and wired-AND, and this check may go away.
        if len(out_kind) != 1:
            out_member_paths_as_string = \
                ", ".join(_format_path(h) for h, m in out_kind)
            raise ConnectionError(
                f"Cannot connect several output members {out_member_paths_as_string} together")
        # There is exactly one Out member after this point, and any amount of In members.
        # Traversing the paths to all of them should always succeed, since the signature check
        # at the beginning of `connect()` passed, and so should casting the result to a Value.
        (out_path, out_member), = out_kind
        for (in_path, in_member) in in_kind:
            def connect_value(*, out_path, in_path):
                in_value = Value.cast(_traverse_path(in_path, objects))
                out_value = Value.cast(_traverse_path(out_path, objects))
                assert type(in_value) in (Const, Signal)
                # If the input is a constant, only a constant may be connected to it. Ensure that
                # this is the case.
                if type(in_value) is Const:
                    # If the output is not a constant, the connection is illegal.
                    if type(out_value) is not Const:
                        raise ConnectionError(
                            f"Cannot connect to the input member {_format_path(in_path)} that has "
                            f"a constant value {in_value.value!r}")
                    # If the output is a constant, the connection is legal only if the value is
                    # the same for both the input and the output.
                    if type(out_value) is Const and in_value.value != out_value.value:
                        raise ConnectionError(
                            f"Cannot connect input member {_format_path(in_path)} that has "
                            f"a constant value {in_value.value!r} to an output member "
                            f"{_format_path(out_path)} that has a differing constant value "
                            f"{out_value.value!r}")
                    # We never actually connect anything to the constant input; we only ensure its
                    # value (which is constant) is consistent with a connection that would have
                    # been made.
                    return
                # A connection that is made at this point is guaranteed to be valid.
                connections.append(in_value.eq(out_value))
            def connect_dimensions(dimensions, *, out_path, in_path):
                if not dimensions:
                    return connect_value(out_path=out_path, in_path=in_path)
                dimension, *rest_of_dimensions = dimensions
                for index in range(dimension):
                    connect_dimensions(rest_of_dimensions,
                                       out_path=(*out_path, index), in_path=(*in_path, index))
            assert out_member.dimensions == in_member.dimensions
            connect_dimensions(out_member.dimensions, out_path=out_path, in_path=in_path)
    # Now that we know all of the connections are legal, add them to the module. This is done
    # instead of returning them because adding them to a non-comb domain would subtly violate
    # assumptions that `connect()` is intended to provide.
    m.d.comb += connections


class Component(Elaboratable):
    def __init__(self):
        for name in self.signature.members:
            if hasattr(self, name):
                raise NameError(f"Cannot initialize attribute for signature member {name!r} "
                                f"because an attribute with the same name already exists")
        self.__dict__.update(self.signature.members.create())

    # TODO(py3.9): This should be a class method, but descriptors don't stack this way
    # in Python 3.8 and below.
    # @classmethod
    @property
    def signature(self):
        cls = type(self)
        signature = Signature({})
        for base in cls.mro()[:cls.mro().index(Component)]:
            for name, annot in getattr(base, "__annotations__", {}).items():
                if name.startswith("_"):
                    continue
                if (annot is Value or annot is Signal or annot is Const or
                        (isinstance(annot, type) and issubclass(annot, ValueCastable)) or
                        isinstance(annot, Signature)):
                    if isinstance(annot, type):
                        annot_repr = annot.__name__
                    else:
                        annot_repr = repr(annot)
                    # To suppress this warning in the rare cases where it is necessary (and naming
                    # the field with a leading underscore is infeasible), override the property.
                    warnings.warn(
                        message=f"Component '{cls.__module__}.{cls.__qualname__}' has "
                                f"an annotation '{name}: {annot_repr}', which is not "
                                f"a signature member; did you mean '{name}: In({annot_repr})' "
                                f"or '{name}: Out({annot_repr})'?",
                        category=SyntaxWarning,
                        stacklevel=2)
                elif type(annot) is Member:
                    signature.members[name] = annot
        if not signature.members:
            raise NotImplementedError(
                f"Component '{cls.__module__}.{cls.__qualname__}' does not have signature member "
                f"annotations")
        return signature


# amaranth/sim/__init__.py

from .core import *


__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]


# amaranth/sim/_base.py

__all__ = ["BaseProcess", "BaseSignalState", "BaseSimulation", "BaseEngine"]


class BaseProcess:
    __slots__ = ()

    def __init__(self):
        self.reset()

    def reset(self):
        self.runnable = False
        self.passive  = True

    def run(self):
        raise NotImplementedError


class BaseSignalState:
    __slots__ = ()

    signal = NotImplemented

    curr = NotImplemented
    next = NotImplemented

    def set(self, value):
        raise NotImplementedError


class BaseSimulation:
    def reset(self):
        raise NotImplementedError

    def get_signal(self, signal):
        raise NotImplementedError

    slots = NotImplemented

    def add_trigger(self, process, signal, *, trigger=None):
        raise NotImplementedError

    def remove_trigger(self, process, signal):
        raise NotImplementedError

    def wait_interval(self, process, interval):
        raise NotImplementedError


class BaseEngine:
    def add_coroutine_process(self, process, *, default_cmd):
        raise NotImplementedError

    def add_clock_process(self, clock, *, phase, period):
        raise NotImplementedError

    def reset(self):
        raise NotImplementedError

    @property
    def now(self):
        raise NotImplementedError

    def advance(self):
        raise NotImplementedError

    def write_vcd(self, *, vcd_file, gtkw_file, traces):
        raise NotImplementedError


# amaranth/sim/_pyclock.py

from ._base import BaseProcess


__all__ = ["PyClockProcess"]


class PyClockProcess(BaseProcess):
    def __init__(self, state, signal, *, phase, period):
        assert len(signal) == 1

        self.state  = state
        self.slot   = self.state.get_signal(signal)
        self.phase  = phase
        self.period = period

        self.reset()

    def reset(self):
        self.runnable = True
        self.passive = True

        self.initial = True

    def run(self):
        self.runnable = False

        if self.initial:
            self.initial = False
            self.state.wait_interval(self, self.phase)

        else:
            clk_state = self.state.slots[self.slot]
            clk_state.set(not clk_state.curr)
            self.state.wait_interval(self, self.period // 2)


# amaranth/sim/_pycoro.py

import inspect

from ..hdl import *
from ..hdl.ast import Statement, SignalSet
from .core import Tick, Settle, Delay, Passive, Active
from ._base import BaseProcess
from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler


__all__ = ["PyCoroProcess"]


class PyCoroProcess(BaseProcess):
    def __init__(self, state, domains, constructor, *, default_cmd=None):
        self.state = state
        self.domains = domains
        self.constructor = constructor
        self.default_cmd = default_cmd

        self.reset()

    def reset(self):
        self.runnable = True
        self.passive = False

        self.coroutine = self.constructor()
        self.exec_locals = {
            "slots": self.state.slots,
            "result": None,
            **_ValueCompiler.helpers
        }
        self.waits_on = SignalSet()

    def src_loc(self):
        coroutine = self.coroutine
        if coroutine is None:
            return None
        while coroutine.gi_yieldfrom is not None and inspect.isgenerator(coroutine.gi_yieldfrom):
            coroutine = coroutine.gi_yieldfrom
        if inspect.isgenerator(coroutine):
            frame = coroutine.gi_frame
        if inspect.iscoroutine(coroutine):
            frame = coroutine.cr_frame
        return "{}:{}".format(inspect.getfile(frame), inspect.getlineno(frame))

    def add_trigger(self, signal, trigger=None):
        self.state.add_trigger(self, signal, trigger=trigger)
        self.waits_on.add(signal)

    def clear_triggers(self):
        for signal in self.waits_on:
            self.state.remove_trigger(self, signal)
        self.waits_on.clear()

    def run(self):
        if self.coroutine is None:
            return

        self.clear_triggers()

        response = None
        while True:
            try:
                command = self.coroutine.send(response)
                if command is None:
                    command = self.default_cmd
                response = None

                if isinstance(command, Value):
                    exec(_RHSValueCompiler.compile(self.state, command, mode="curr"),
                        self.exec_locals)
                    response = Const(self.exec_locals["result"], command.shape()).value

                elif isinstance(command, Statement):
                    exec(_StatementCompiler.compile(self.state, command),
                        self.exec_locals)

                elif type(command) is Tick:
                    domain = command.domain
                    if isinstance(domain, ClockDomain):
                        pass
                    elif domain in self.domains:
                        domain = self.domains[domain]
                    else:
                        raise NameError("Received command {!r} that refers to a nonexistent "
                                        "domain {!r} from process {!r}"
                                        .format(command, command.domain, self.src_loc()))
                    self.add_trigger(domain.clk, trigger=1 if domain.clk_edge == "pos" else 0)
                    if domain.rst is not None and domain.async_reset:
                        self.add_trigger(domain.rst, trigger=1)
                    return

                elif type(command) is Settle:
                    self.state.wait_interval(self, None)
                    return

                elif type(command) is Delay:
                    # Internal timeline is in 1ps integeral units, intervals are public API and in floating point
                    interval = int(command.interval * 1e12) if command.interval is not None else None
                    self.state.wait_interval(self, interval)
                    return

                elif type(command) is Passive:
                    self.passive = True

                elif type(command) is Active:
                    self.passive = False

                elif command is None: # only possible if self.default_cmd is None
                    raise TypeError("Received default command from process {!r} that was added "
                                    "with add_process(); did you mean to add this process with "
                                    "add_sync_process() instead?"
                                    .format(self.src_loc()))

                else:
                    raise TypeError("Received unsupported command {!r} from process {!r}"
                                    .format(command, self.src_loc()))

            except StopIteration:
                self.passive = True
                self.coroutine = None
                return

            except Exception as exn:
                self.coroutine.throw(exn)


# amaranth/sim/_pyrtl.py

import os
import tempfile
from contextlib import contextmanager
import sys

from ..hdl import *
from ..hdl.ast import SignalSet
from ..hdl.xfrm import ValueVisitor, StatementVisitor, LHSGroupFilter
from ._base import BaseProcess


__all__ = ["PyRTLProcess"]

_USE_PATTERN_MATCHING = (sys.version_info >= (3, 10))

class PyRTLProcess(BaseProcess):
    __slots__ = ("is_comb", "runnable", "passive", "run")

    def __init__(self, *, is_comb):
        self.is_comb  = is_comb

        self.reset()

    def reset(self):
        self.runnable = self.is_comb
        self.passive  = True


class _PythonEmitter:
    def __init__(self):
        self._buffer = []
        self._suffix = 0
        self._level  = 0

    def append(self, code):
        self._buffer.append("    " * self._level)
        self._buffer.append(code)
        self._buffer.append("\n")

    @contextmanager
    def indent(self):
        self._level += 1
        yield
        self._level -= 1

    def flush(self, indent=""):
        code = "".join(self._buffer)
        self._buffer.clear()
        return code

    def gen_var(self, prefix):
        name = f"{prefix}_{self._suffix}"
        self._suffix += 1
        return name

    def def_var(self, prefix, value):
        name = self.gen_var(prefix)
        self.append(f"{name} = {value}")
        return name


class _Compiler:
    def __init__(self, state, emitter):
        self.state = state
        self.emitter = emitter


class _ValueCompiler(ValueVisitor, _Compiler):
    helpers = {
        "sign": lambda value, sign: value | sign if value & sign else value,
        "zdiv": lambda lhs, rhs: 0 if rhs == 0 else lhs // rhs,
        "zmod": lambda lhs, rhs: 0 if rhs == 0 else lhs % rhs,
    }

    def on_value(self, value):
        # Very large values are unlikely to compile or simulate in reasonable time.
        if len(value) > 2 ** 16:
            if value.src_loc:
                src = "{}:{}".format(*value.src_loc)
            else:
                src = "unknown location"
            raise OverflowError("Value defined at {} is {} bits wide, which is unlikely to "
                                "simulate in reasonable time"
                                .format(src, len(value)))

        v = super().on_value(value)
        if isinstance(v, str) and len(v) > 1000:
            # Avoid parser stack overflow on older Pythons.
            return self.emitter.def_var("intermediate", v)
        return v

    def on_ClockSignal(self, value):
        raise NotImplementedError # :nocov:

    def on_ResetSignal(self, value):
        raise NotImplementedError # :nocov:

    def on_AnyConst(self, value):
        raise NotImplementedError # :nocov:

    def on_AnySeq(self, value):
        raise NotImplementedError # :nocov:

    def on_Sample(self, value):
        raise NotImplementedError # :nocov:

    def on_Initial(self, value):
        raise NotImplementedError # :nocov:


class _RHSValueCompiler(_ValueCompiler):
    def __init__(self, state, emitter, *, mode, inputs=None):
        super().__init__(state, emitter)
        assert mode in ("curr", "next")
        self.mode = mode
        # If not None, `inputs` gets populated with RHS signals.
        self.inputs = inputs

    def on_Const(self, value):
        return f"{value.value}"

    def on_Signal(self, value):
        if self.inputs is not None:
            self.inputs.add(value)

        if self.mode == "curr":
            return f"slots[{self.state.get_signal(value)}].{self.mode}"
        else:
            return f"next_{self.state.get_signal(value)}"

    def on_Operator(self, value):
        def mask(value):
            value_mask = (1 << len(value)) - 1
            return f"({value_mask:#x} & {self(value)})"

        def sign(value):
            if value.shape().signed:
                return f"sign({mask(value)}, {-1 << (len(value) - 1):#x})"
            else: # unsigned
                return mask(value)

        if len(value.operands) == 1:
            arg, = value.operands
            if value.operator == "~":
                return f"(~{mask(arg)})"
            if value.operator == "-":
                return f"(-{sign(arg)})"
            if value.operator == "b":
                return f"bool({mask(arg)})"
            if value.operator == "r|":
                return f"(0 != {mask(arg)})"
            if value.operator == "r&":
                return f"({(1 << len(arg)) - 1} == {mask(arg)})"
            if value.operator == "r^":
                # Believe it or not, this is the fastest way to compute a sideways XOR in Python.
                return f"(format({mask(arg)}, 'b').count('1') % 2)"
            if value.operator in ("u", "s"):
                # These operators don't change the bit pattern, only its interpretation.
                return self(arg)
        elif len(value.operands) == 2:
            lhs, rhs = value.operands
            if value.operator == "+":
                return f"({sign(lhs)} + {sign(rhs)})"
            if value.operator == "-":
                return f"({sign(lhs)} - {sign(rhs)})"
            if value.operator == "*":
                return f"({sign(lhs)} * {sign(rhs)})"
            if value.operator == "//":
                return f"zdiv({sign(lhs)}, {sign(rhs)})"
            if value.operator == "%":
                return f"zmod({sign(lhs)}, {sign(rhs)})"
            if value.operator == "&":
                return f"({mask(lhs)} & {mask(rhs)})"
            if value.operator == "|":
                return f"({mask(lhs)} | {mask(rhs)})"
            if value.operator == "^":
                return f"({mask(lhs)} ^ {mask(rhs)})"
            if value.operator == "<<":
                return f"({sign(lhs)} << {sign(rhs)})"
            if value.operator == ">>":
                return f"({sign(lhs)} >> {sign(rhs)})"
            if value.operator == "==":
                return f"({sign(lhs)} == {sign(rhs)})"
            if value.operator == "!=":
                return f"({sign(lhs)} != {sign(rhs)})"
            if value.operator == "<":
                return f"({sign(lhs)} < {sign(rhs)})"
            if value.operator == "<=":
                return f"({sign(lhs)} <= {sign(rhs)})"
            if value.operator == ">":
                return f"({sign(lhs)} > {sign(rhs)})"
            if value.operator == ">=":
                return f"({sign(lhs)} >= {sign(rhs)})"
        elif len(value.operands) == 3:
            if value.operator == "m":
                sel, val1, val0 = value.operands
                return f"({self(val1)} if {mask(sel)} else {self(val0)})"
        raise NotImplementedError("Operator '{}' not implemented".format(value.operator)) # :nocov:

    def on_Slice(self, value):
        return f"({(1 << len(value)) - 1:#x} & ({self(value.value)} >> {value.start}))"

    def on_Part(self, value):
        offset_mask = (1 << len(value.offset)) - 1
        offset = f"({value.stride} * ({offset_mask:#x} & {self(value.offset)}))"
        return f"({(1 << value.width) - 1} & " \
               f"{self(value.value)} >> {offset})"

    def on_Cat(self, value):
        gen_parts = []
        offset = 0
        for part in value.parts:
            part_mask = (1 << len(part)) - 1
            gen_parts.append(f"(({part_mask:#x} & {self(part)}) << {offset})")
            offset += len(part)
        if gen_parts:
            return f"({' | '.join(gen_parts)})"
        return f"0"

    def on_ArrayProxy(self, value):
        index_mask = (1 << len(value.index)) - 1
        gen_index = self.emitter.def_var("rhs_index", f"{index_mask:#x} & {self(value.index)}")
        gen_value = self.emitter.gen_var("rhs_proxy")
        if value.elems:
            if _USE_PATTERN_MATCHING:
                self.emitter.append(f"match {gen_index}:")
                with self.emitter.indent():
                    for index, elem in enumerate(value.elems):
                        self.emitter.append(f"case {index}:")
                        with self.emitter.indent():
                            self.emitter.append(f"{gen_value} = {self(elem)}")
                    self.emitter.append("case _:")
                    with self.emitter.indent():
                            self.emitter.append(f"{gen_value} = {self(value.elems[-1])}")
            else:
                for index, elem in enumerate(value.elems):
                    if index == 0:
                        self.emitter.append(f"if {index} == {gen_index}:")
                    else:
                        self.emitter.append(f"elif {index} == {gen_index}:")
                    with self.emitter.indent():
                        self.emitter.append(f"{gen_value} = {self(elem)}")
                self.emitter.append(f"else:")
                with self.emitter.indent():
                    self.emitter.append(f"{gen_value} = {self(value.elems[-1])}")

            return gen_value
        else:
            return f"0"

    @classmethod
    def compile(cls, state, value, *, mode):
        emitter = _PythonEmitter()
        compiler = cls(state, emitter, mode=mode)
        emitter.append(f"result = {compiler(value)}")
        return emitter.flush()


class _LHSValueCompiler(_ValueCompiler):
    def __init__(self, state, emitter, *, rhs, outputs=None):
        super().__init__(state, emitter)
        # `rrhs` is used to translate rvalues that are syntactically a part of an lvalue, e.g.
        # the offset of a Part.
        self.rrhs = rhs
        # `lrhs` is used to translate the read part of a read-modify-write cycle during partial
        # update of an lvalue.
        self.lrhs = _RHSValueCompiler(state, emitter, mode="next", inputs=None)
        # If not None, `outputs` gets populated with signals on LHS.
        self.outputs = outputs

    def on_Const(self, value):
        raise TypeError # :nocov:

    def on_Signal(self, value):
        if self.outputs is not None:
            self.outputs.add(value)

        def gen(arg):
            value_mask = (1 << len(value)) - 1
            if value.shape().signed:
                value_sign = f"sign({value_mask:#x} & {arg}, {-1 << (len(value) - 1)})"
            else: # unsigned
                value_sign = f"{value_mask:#x} & {arg}"
            self.emitter.append(f"next_{self.state.get_signal(value)} = {value_sign}")
        return gen

    def on_Operator(self, value):
        if value.operator in ("u", "s"):
            return self(value.operands[0])
        raise TypeError # :nocov:

    def on_Slice(self, value):
        def gen(arg):
            width_mask = (1 << (value.stop - value.start)) - 1
            self(value.value)(f"({self.lrhs(value.value)} & " \
                f"{~(width_mask << value.start):#x} | " \
                f"(({width_mask:#x} & {arg}) << {value.start}))")
        return gen

    def on_Part(self, value):
        def gen(arg):
            width_mask = (1 << value.width) - 1
            offset_mask = (1 << len(value.offset)) - 1
            offset = f"({value.stride} * ({offset_mask:#x} & {self.rrhs(value.offset)}))"
            self(value.value)(f"({self.lrhs(value.value)} & " \
                f"~({width_mask:#x} << {offset}) | " \
                f"(({width_mask:#x} & {arg}) << {offset}))")
        return gen

    def on_Cat(self, value):
        def gen(arg):
            gen_arg = self.emitter.def_var("cat", arg)
            offset = 0
            for part in value.parts:
                part_mask = (1 << len(part)) - 1
                self(part)(f"({part_mask:#x} & ({gen_arg} >> {offset}))")
                offset += len(part)
        return gen

    def on_ArrayProxy(self, value):
        def gen(arg):
            index_mask = (1 << len(value.index)) - 1
            gen_index = self.emitter.def_var("index", f"{self.rrhs(value.index)} & {index_mask:#x}")
            if value.elems:
                if _USE_PATTERN_MATCHING:
                    self.emitter.append(f"match {gen_index}:")
                    with self.emitter.indent():
                        for index, elem in enumerate(value.elems):
                            self.emitter.append(f"case {index}:")
                            with self.emitter.indent():
                                self(elem)(arg)
                        self.emitter.append("case _:")
                        with self.emitter.indent():
                            self(value.elems[-1])(arg)
                else:
                    for index, elem in enumerate(value.elems):
                        if index == 0:
                            self.emitter.append(f"if {index} == {gen_index}:")
                        else:
                            self.emitter.append(f"elif {index} == {gen_index}:")
                        with self.emitter.indent():
                            self(elem)(arg)
                    self.emitter.append(f"else:")
                    with self.emitter.indent():
                        self(value.elems[-1])(arg)
            else:
                self.emitter.append(f"pass")
        return gen


class _StatementCompiler(StatementVisitor, _Compiler):
    def __init__(self, state, emitter, *, inputs=None, outputs=None):
        super().__init__(state, emitter)
        self.rhs = _RHSValueCompiler(state, emitter, mode="curr", inputs=inputs)
        self.lhs = _LHSValueCompiler(state, emitter, rhs=self.rhs, outputs=outputs)

    def on_statements(self, stmts):
        for stmt in stmts:
            self(stmt)
        if not stmts:
            self.emitter.append("pass")

    def on_Assign(self, stmt):
        gen_rhs_value = self.rhs(stmt.rhs) # check for oversized value before generating mask
        gen_rhs = f"({(1 << len(stmt.rhs)) - 1:#x} & {gen_rhs_value})"
        if stmt.rhs.shape().signed:
            gen_rhs = f"sign({gen_rhs}, {-1 << (len(stmt.rhs) - 1):#x})"
        return self.lhs(stmt.lhs)(gen_rhs)

    def on_Switch(self, stmt):
        gen_test_value = self.rhs(stmt.test) # check for oversized value before generating mask
        gen_test = self.emitter.def_var("test", f"{(1 << len(stmt.test)) - 1:#x} & {gen_test_value}")
        for index, (patterns, stmts) in enumerate(stmt.cases.items()):
            gen_checks = []
            if not patterns:
                gen_checks.append(f"True")
            else:
                for pattern in patterns:
                    if "-" in pattern:
                        mask  = int("".join("0" if b == "-" else "1" for b in pattern), 2)
                        value = int("".join("0" if b == "-" else  b  for b in pattern), 2)
                        gen_checks.append(f"{value} == ({mask} & {gen_test})")
                    else:
                        value = int(pattern, 2)
                        gen_checks.append(f"{value} == {gen_test}")
            if index == 0:
                self.emitter.append(f"if {' or '.join(gen_checks)}:")
            else:
                self.emitter.append(f"elif {' or '.join(gen_checks)}:")
            with self.emitter.indent():
                self(stmts)

    def on_Assert(self, stmt):
        raise NotImplementedError # :nocov:

    def on_Assume(self, stmt):
        raise NotImplementedError # :nocov:

    def on_Cover(self, stmt):
        raise NotImplementedError # :nocov:

    @classmethod
    def compile(cls, state, stmt):
        output_indexes = [state.get_signal(signal) for signal in stmt._lhs_signals()]
        emitter = _PythonEmitter()
        for signal_index in output_indexes:
            emitter.append(f"next_{signal_index} = slots[{signal_index}].next")
        compiler = cls(state, emitter)
        compiler(stmt)
        for signal_index in output_indexes:
            emitter.append(f"slots[{signal_index}].set(next_{signal_index})")
        return emitter.flush()


class _FragmentCompiler:
    def __init__(self, state):
        self.state = state

    def __call__(self, fragment):
        processes = set()

        for domain_name, domain_signals in fragment.drivers.items():
            domain_stmts = LHSGroupFilter(domain_signals)(fragment.statements)
            domain_process = PyRTLProcess(is_comb=domain_name is None)

            emitter = _PythonEmitter()
            emitter.append(f"def run():")
            emitter._level += 1

            if domain_name is None:
                for signal in domain_signals:
                    signal_index = self.state.get_signal(signal)
                    emitter.append(f"next_{signal_index} = {signal.reset}")

                inputs = SignalSet()
                _StatementCompiler(self.state, emitter, inputs=inputs)(domain_stmts)

                for input in inputs:
                    self.state.add_trigger(domain_process, input)

            else:
                domain = fragment.domains[domain_name]
                clk_trigger = 1 if domain.clk_edge == "pos" else 0
                self.state.add_trigger(domain_process, domain.clk, trigger=clk_trigger)
                if domain.rst is not None and domain.async_reset:
                    rst_trigger = 1
                    self.state.add_trigger(domain_process, domain.rst, trigger=rst_trigger)

                for signal in domain_signals:
                    signal_index = self.state.get_signal(signal)
                    emitter.append(f"next_{signal_index} = slots[{signal_index}].next")

                _StatementCompiler(self.state, emitter)(domain_stmts)

            for signal in domain_signals:
                signal_index = self.state.get_signal(signal)
                emitter.append(f"slots[{signal_index}].set(next_{signal_index})")

            # There shouldn't be any exceptions raised by the generated code, but if there are
            # (almost certainly due to a bug in the code generator), use this environment variable
            # to make backtraces useful.
            code = emitter.flush()
            if os.getenv("AMARANTH_pysim_dump"):
                file = tempfile.NamedTemporaryFile("w", prefix="amaranth_pysim_", delete=False)
                file.write(code)
                filename = file.name
            else:
                filename = "<string>"

            exec_locals = {"slots": self.state.slots, **_ValueCompiler.helpers}
            exec(compile(code, filename, "exec"), exec_locals)
            domain_process.run = exec_locals["run"]

            processes.add(domain_process)

        for subfragment_index, (subfragment, subfragment_name) in enumerate(fragment.subfragments):
            if subfragment_name is None:
                subfragment_name = "U${}".format(subfragment_index)
            processes.update(self(subfragment))

        return processes


# amaranth/sim/core.py

import inspect
import warnings

from .._utils import deprecated
from ..hdl.cd import *
from ..hdl.ir import *
from ._base import BaseEngine


__all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"]


class Command:
    pass


class Settle(Command):
    def __repr__(self):
        return "(settle)"


class Delay(Command):
    def __init__(self, interval=None):
        self.interval = None if interval is None else float(interval)

    def __repr__(self):
        if self.interval is None:
            return "(delay ε)"
        else:
            return "(delay {:.3}us)".format(self.interval * 1e6)


class Tick(Command):
    def __init__(self, domain="sync"):
        if not isinstance(domain, (str, ClockDomain)):
            raise TypeError("Domain must be a string or a ClockDomain instance, not {!r}"
                            .format(domain))
        assert domain != "comb"
        self.domain = domain

    def __repr__(self):
        return "(tick {})".format(self.domain)


class Passive(Command):
    def __repr__(self):
        return "(passive)"


class Active(Command):
    def __repr__(self):
        return "(active)"


class Simulator:
    def __init__(self, fragment, *, engine="pysim"):
        if isinstance(engine, type) and issubclass(engine, BaseEngine):
            pass
        elif engine == "pysim":
            from .pysim import PySimEngine
            engine = PySimEngine
        else:
            raise TypeError("Value '{!r}' is not a simulation engine class or "
                            "a simulation engine name"
                            .format(engine))

        self._fragment = Fragment.get(fragment, platform=None).prepare()
        self._engine   = engine(self._fragment)
        self._clocked  = set()

    def _check_process(self, process):
        if not (inspect.isgeneratorfunction(process) or inspect.iscoroutinefunction(process)):
            raise TypeError("Cannot add a process {!r} because it is not a generator function"
                            .format(process))
        return process

    def add_process(self, process):
        process = self._check_process(process)
        def wrapper():
            # Only start a bench process after comb settling, so that the reset values are correct.
            yield Settle()
            yield from process()
        self._engine.add_coroutine_process(wrapper, default_cmd=None)

    def add_sync_process(self, process, *, domain="sync"):
        process = self._check_process(process)
        def wrapper():
            # Only start a sync process after the first clock edge (or reset edge, if the domain
            # uses an asynchronous reset). This matches the behavior of synchronous FFs.
            yield Tick(domain)
            yield from process()
        self._engine.add_coroutine_process(wrapper, default_cmd=Tick(domain))

    def add_clock(self, period, *, phase=None, domain="sync", if_exists=False):
        """Add a clock process.

        Adds a process that drives the clock signal of ``domain`` at a 50% duty cycle.

        Arguments
        ---------
        period : float
            Clock period. The process will toggle the ``domain`` clock signal every ``period / 2``
            seconds.
        phase : None or float
            Clock phase. The process will wait ``phase`` seconds before the first clock transition.
            If not specified, defaults to ``period / 2``.
        domain : str or ClockDomain
            Driven clock domain. If specified as a string, the domain with that name is looked up
            in the root fragment of the simulation.
        if_exists : bool
            If ``False`` (the default), raise an error if the driven domain is specified as
            a string and the root fragment does not have such a domain. If ``True``, do nothing
            in this case.
        """
        if isinstance(domain, ClockDomain):
            if (domain.name in self._fragment.domains and
                    domain is not self._fragment.domains[domain.name]):
                warnings.warn("Adding a clock process that drives a clock domain object "
                              "named {!r}, which is distinct from an identically named domain "
                              "in the simulated design"
                              .format(domain.name),
                              UserWarning, stacklevel=2)
        elif domain in self._fragment.domains:
            domain = self._fragment.domains[domain]
        elif if_exists:
            return
        else:
            raise ValueError("Domain {!r} is not present in simulation"
                             .format(domain))
        if domain in self._clocked:
            raise ValueError("Domain {!r} already has a clock driving it"
                             .format(domain.name))
        
        # We represent times internally in 1 ps units, but users supply float quantities of seconds
        period = int(period * 1e12)

        if phase is None:
            # By default, delay the first edge by half period. This causes any synchronous activity
            # to happen at a non-zero time, distinguishing it from the reset values in the waveform
            # viewer.
            phase = period // 2
        else:
            phase = int(phase * 1e12) + period // 2
        self._engine.add_clock_process(domain.clk, phase=phase, period=period)
        self._clocked.add(domain)

    def reset(self):
        """Reset the simulation.

        Assign the reset value to every signal in the simulation, and restart every user process.
        """
        self._engine.reset()

    def advance(self):
        """Advance the simulation.

        Run every process and commit changes until a fixed point is reached, then advance time
        to the closest deadline (if any). If there is an unstable combinatorial loop,
        this function will never return.

        Returns ``True`` if there are any active processes, ``False`` otherwise.
        """
        return self._engine.advance()

    def run(self):
        """Run the simulation while any processes are active.

        Processes added with :meth:`add_process` and :meth:`add_sync_process` are initially active,
        and may change their status using the ``yield Passive()`` and ``yield Active()`` commands.
        Processes compiled from HDL and added with :meth:`add_clock` are always passive.
        """
        while self.advance():
            pass

    def run_until(self, deadline, *, run_passive=False):
        """Run the simulation until it advances to ``deadline``.

        If ``run_passive`` is ``False``, the simulation also stops when there are no active
        processes, similar to :meth:`run`. Otherwise, the simulation will stop only after it
        advances to or past ``deadline``.

        If the simulation stops advancing, this function will never return.
        """
        # Convert deadline in seconds into internal 1 ps units
        deadline = deadline * 1e12
        assert self._engine.now <= deadline
        while (self.advance() or run_passive) and self._engine.now < deadline:
            pass

    def write_vcd(self, vcd_file, gtkw_file=None, *, traces=()):
        """Write waveforms to a Value Change Dump file, optionally populating a GTKWave save file.

        This method returns a context manager. It can be used as: ::

            sim = Simulator(frag)
            sim.add_clock(1e-6)
            with sim.write_vcd("dump.vcd", "dump.gtkw"):
                sim.run_until(1e-3)

        Arguments
        ---------
        vcd_file : str or file-like object
            Verilog Value Change Dump file or filename.
        gtkw_file : str or file-like object
            GTKWave save file or filename.
        traces : iterable of Signal
            Signals to display traces for.
        """
        if self._engine.now != 0:
            for file in (vcd_file, gtkw_file):
                if hasattr(file, "close"):
                    file.close()
            raise ValueError("Cannot start writing waveforms after advancing simulation time")

        return self._engine.write_vcd(vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)


# amaranth/sim/pysim.py

from contextlib import contextmanager
import itertools
import re
from vcd import VCDWriter
from vcd.gtkw import GTKWSave

from ..hdl import *
from ..hdl.ast import SignalDict
from ._base import *
from ._pyrtl import _FragmentCompiler
from ._pycoro import PyCoroProcess
from ._pyclock import PyClockProcess


__all__ = ["PySimEngine"]


class _NameExtractor:
    def __init__(self):
        self.names = SignalDict()

    def __call__(self, fragment, *, hierarchy=("bench", "top",)):
        def add_signal_name(signal):
            hierarchical_signal_name = (*hierarchy, signal.name)
            if signal not in self.names:
                self.names[signal] = {hierarchical_signal_name}
            else:
                self.names[signal].add(hierarchical_signal_name)

        for domain_name, domain_signals in fragment.drivers.items():
            if domain_name is not None:
                domain = fragment.domains[domain_name]
                add_signal_name(domain.clk)
                if domain.rst is not None:
                    add_signal_name(domain.rst)

        for statement in fragment.statements:
            for signal in statement._lhs_signals() | statement._rhs_signals():
                if not isinstance(signal, (ClockSignal, ResetSignal)):
                    add_signal_name(signal)

        for subfragment_index, (subfragment, subfragment_name) in enumerate(fragment.subfragments):
            if subfragment_name is None:
                subfragment_name = "U${}".format(subfragment_index)
            self(subfragment, hierarchy=(*hierarchy, subfragment_name))

        return self.names


class _VCDWriter:
    @staticmethod
    def decode_to_vcd(signal, value):
        return signal.decoder(value).expandtabs().replace(" ", "_")

    def __init__(self, fragment, *, vcd_file, gtkw_file=None, traces=()):
        if isinstance(vcd_file, str):
            vcd_file = open(vcd_file, "wt")
        if isinstance(gtkw_file, str):
            gtkw_file = open(gtkw_file, "wt")

        self.vcd_vars = SignalDict()
        self.vcd_file = vcd_file
        self.vcd_writer = vcd_file and VCDWriter(self.vcd_file,
            timescale="1 ps", comment="Generated by Amaranth")

        self.gtkw_names = SignalDict()
        self.gtkw_file = gtkw_file
        self.gtkw_save = gtkw_file and GTKWSave(self.gtkw_file)

        self.traces = []

        signal_names = _NameExtractor()(fragment)

        trace_names = SignalDict()
        for trace in traces:
            if trace not in signal_names:
                trace_names[trace] = {('bench', trace.name)}
            self.traces.append(trace)

        if self.vcd_writer is None:
            return

        for signal, names in itertools.chain(signal_names.items(), trace_names.items()):
            if signal.decoder:
                var_type = "string"
                var_size = 1
                var_init = self.decode_to_vcd(signal, signal.reset)
            else:
                var_type = "wire"
                var_size = signal.width
                var_init = signal.reset

            for (*var_scope, var_name) in names:
                if re.search(r"[ \t\r\n]", var_name):
                    raise NameError("Signal '{}.{}' contains a whitespace character"
                                    .format(".".join(var_scope), var_name))

                suffix = None
                while True:
                    try:
                        if suffix is None:
                            var_name_suffix = var_name
                        else:
                            var_name_suffix = "{}${}".format(var_name, suffix)
                        if signal not in self.vcd_vars:
                            vcd_var = self.vcd_writer.register_var(
                                scope=var_scope, name=var_name_suffix,
                                var_type=var_type, size=var_size, init=var_init)
                            self.vcd_vars[signal] = vcd_var
                        else:
                            self.vcd_writer.register_alias(
                                scope=var_scope, name=var_name_suffix,
                                var=self.vcd_vars[signal])
                        break
                    except KeyError:
                        suffix = (suffix or 0) + 1

                if signal not in self.gtkw_names:
                    self.gtkw_names[signal] = (*var_scope, var_name_suffix)

    def update(self, timestamp, signal, value):
        vcd_var = self.vcd_vars.get(signal)
        if vcd_var is None:
            return

        if signal.decoder:
            var_value = self.decode_to_vcd(signal, value)
        else:
            var_value = value
        self.vcd_writer.change(vcd_var, timestamp, var_value)

    def close(self, timestamp):
        if self.vcd_writer is not None:
            self.vcd_writer.close(timestamp)

        if self.gtkw_save is not None:
            self.gtkw_save.dumpfile(self.vcd_file.name)
            self.gtkw_save.dumpfile_size(self.vcd_file.tell())

            self.gtkw_save.treeopen("top")
            for signal in self.traces:
                if len(signal) > 1 and not signal.decoder:
                    suffix = "[{}:0]".format(len(signal) - 1)
                else:
                    suffix = ""
                self.gtkw_save.trace(".".join(self.gtkw_names[signal]) + suffix)

        if self.vcd_file is not None:
            self.vcd_file.close()
        if self.gtkw_file is not None:
            self.gtkw_file.close()


class _Timeline:
    def __init__(self):
        self.now = 0
        self.deadlines = dict()

    def reset(self):
        self.now = 0
        self.deadlines.clear()

    def at(self, run_at, process):
        assert process not in self.deadlines
        self.deadlines[process] = run_at

    def delay(self, delay_by, process):
        if delay_by is None:
            run_at = self.now
        else:
            run_at = self.now + delay_by
        self.at(run_at, process)

    def advance(self):
        nearest_processes = set()
        nearest_deadline = None
        for process, deadline in self.deadlines.items():
            if deadline is None:
                if nearest_deadline is not None:
                    nearest_processes.clear()
                nearest_processes.add(process)
                nearest_deadline = self.now
                break
            elif nearest_deadline is None or deadline <= nearest_deadline:
                assert deadline >= self.now
                if nearest_deadline is not None and deadline < nearest_deadline:
                    nearest_processes.clear()
                nearest_processes.add(process)
                nearest_deadline = deadline

        if not nearest_processes:
            return False

        for process in nearest_processes:
            process.runnable = True
            del self.deadlines[process]
        self.now = nearest_deadline

        return True


class _PySignalState(BaseSignalState):
    __slots__ = ("signal", "curr", "next", "waiters", "pending")

    def __init__(self, signal, pending):
        self.signal = signal
        self.pending = pending
        self.waiters = dict()
        self.curr = self.next = signal.reset

    def set(self, value):
        if self.next == value:
            return
        self.next = value
        self.pending.add(self)

    def commit(self):
        if self.curr == self.next:
            return False
        self.curr = self.next

        awoken_any = False
        for process, trigger in self.waiters.items():
            if trigger is None or trigger == self.curr:
                process.runnable = awoken_any = True
        return awoken_any


class _PySimulation(BaseSimulation):
    def __init__(self):
        self.timeline = _Timeline()
        self.signals  = SignalDict()
        self.slots    = []
        self.pending  = set()

    def reset(self):
        self.timeline.reset()
        for signal, index in self.signals.items():
            self.slots[index].curr = self.slots[index].next = signal.reset
        self.pending.clear()

    def get_signal(self, signal):
        try:
            return self.signals[signal]
        except KeyError:
            index = len(self.slots)
            self.slots.append(_PySignalState(signal, self.pending))
            self.signals[signal] = index
            return index

    def add_trigger(self, process, signal, *, trigger=None):
        index = self.get_signal(signal)
        assert (process not in self.slots[index].waiters or
                self.slots[index].waiters[process] == trigger)
        self.slots[index].waiters[process] = trigger

    def remove_trigger(self, process, signal):
        index = self.get_signal(signal)
        assert process in self.slots[index].waiters
        del self.slots[index].waiters[process]

    def wait_interval(self, process, interval):
        self.timeline.delay(interval, process)

    def commit(self, changed=None):
        converged = True
        for signal_state in self.pending:
            if signal_state.commit():
                converged = False
        if changed is not None:
            changed.update(self.pending)
        self.pending.clear()
        return converged


class PySimEngine(BaseEngine):
    def __init__(self, fragment):
        self._state = _PySimulation()
        self._timeline = self._state.timeline

        self._fragment = fragment
        self._processes = _FragmentCompiler(self._state)(self._fragment)
        self._vcd_writers = []

    def add_coroutine_process(self, process, *, default_cmd):
        self._processes.add(PyCoroProcess(self._state, self._fragment.domains, process,
                                          default_cmd=default_cmd))

    def add_clock_process(self, clock, *, phase, period):
        self._processes.add(PyClockProcess(self._state, clock,
                                           phase=phase, period=period))

    def reset(self):
        self._state.reset()
        for process in self._processes:
            process.reset()

    def _step(self):
        changed = set() if self._vcd_writers else None

        # Performs the two phases of a delta cycle in a loop:
        converged = False
        while not converged:
            # 1. eval: run and suspend every non-waiting process once, queueing signal changes
            for process in self._processes:
                if process.runnable:
                    process.runnable = False
                    process.run()

            # 2. commit: apply every queued signal change, waking up any waiting processes
            converged = self._state.commit(changed)

        for vcd_writer in self._vcd_writers:
            for signal_state in changed:
                vcd_writer.update(self._timeline.now,
                    signal_state.signal, signal_state.curr)

    def advance(self):
        self._step()
        self._timeline.advance()
        return any(not process.passive for process in self._processes)

    @property
    def now(self):
        return self._timeline.now

    @contextmanager
    def write_vcd(self, *, vcd_file, gtkw_file, traces):
        vcd_writer = _VCDWriter(self._fragment,
            vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces)
        try:
            self._vcd_writers.append(vcd_writer)
            yield
        finally:
            vcd_writer.close(self._timeline.now)
            self._vcd_writers.remove(vcd_writer)


# setup.py

from setuptools import setup


def doc_version():
    try:
        from setuptools_scm.git import parse as parse_git
        git = parse_git(".")
        if git.exact:
            return git.format_with("v{tag}")
        else:
            return "latest"
    except ImportError:
        # PEP 517 compliant build tools will never reach this code path.
        # Poetry reaches this code path.
        return ""


setup(
    project_urls={
        "Homepage": "https://amaranth-lang.org/",
        "Documentation": "https://amaranth-lang.org/docs/amaranth/{}".format(doc_version()),
        "Source Code": "https://github.com/amaranth-lang/amaranth",
        "Bug Tracker": "https://github.com/amaranth-lang/amaranth/issues",
    },
)


# amaranth/__init__.py

# Extract version for this package from the environment package metadata. This used to be a lot
# more difficult in earlier Python versions, and the `__version__` field is a legacy of that time.
import importlib.metadata
try:
    __version__ = importlib.metadata.version(__package__)
except importlib.metadata.PackageNotFoundError:
    # No importlib metadata for this package. This shouldn't normally happen, but some people
    # prefer not installing packages via pip at all. Although not recommended we still support it.
    __version__ = "unknown" # :nocov:
del importlib


from .hdl import *


__all__ = [
    "Shape", "unsigned", "signed",
    "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
    "Module",
    "ClockDomain",
    "Elaboratable", "Fragment", "Instance",
    "Memory",
    "Record",
    "DomainRenamer", "ResetInserter", "EnableInserter",
]


# amaranth/_unused.py

import sys
import warnings

from ._utils import get_linter_option


__all__ = ["UnusedMustUse", "MustUse"]


class UnusedMustUse(Warning):
    pass


class MustUse:
    _MustUse__silence = False
    _MustUse__warning = UnusedMustUse

    def __new__(cls, *args, src_loc_at=0, **kwargs):
        frame = sys._getframe(1 + src_loc_at)
        self = super().__new__(cls)
        self._MustUse__used    = False
        self._MustUse__context = dict(
            filename=frame.f_code.co_filename,
            lineno=frame.f_lineno,
            source=self)
        return self

    def __del__(self):
        if self._MustUse__silence:
            return
        if getattr(self._MustUse__warning, "_MustUse__silence", False):
            return
        if hasattr(self, "_MustUse__used") and not self._MustUse__used:
            if get_linter_option(self._MustUse__context["filename"],
                                 self._MustUse__warning.__name__, bool, True):
                warnings.warn_explicit(
                    "{!r} created but never used".format(self), self._MustUse__warning,
                    **self._MustUse__context)


_old_excepthook = sys.excepthook
def _silence_elaboratable(type, value, traceback):
    # Don't show anything if the interpreter crashed; that'd just obscure the exception
    # traceback instead of helping.
    MustUse._MustUse__silence = True
    _old_excepthook(type, value, traceback)
sys.excepthook = _silence_elaboratable


# amaranth/_utils.py

import contextlib
import functools
import warnings
import linecache
import re
from collections import OrderedDict
from collections.abc import Iterable

from .utils import *


__all__ = ["flatten", "union" , "log2_int", "bits_for", "memoize", "final", "deprecated",
           "get_linter_options", "get_linter_option"]


def flatten(i):
    for e in i:
        if isinstance(e, str) or not isinstance(e, Iterable):
            yield e
        else:
            yield from flatten(e)


def union(i, start=None):
    r = start
    for e in i:
        if r is None:
            r = e
        else:
            r |= e
    return r


def memoize(f):
    memo = OrderedDict()
    @functools.wraps(f)
    def g(*args):
        if args not in memo:
            memo[args] = f(*args)
        return memo[args]
    return g


def final(cls):
    def init_subclass():
        raise TypeError("Subclassing {}.{} is not supported"
                        .format(cls.__module__, cls.__name__))
    cls.__init_subclass__ = init_subclass
    return cls


def deprecated(message, stacklevel=2):
    def decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
            return f(*args, **kwargs)
        return wrapper
    return decorator


def _ignore_deprecated(f=None):
    if f is None:
        @contextlib.contextmanager
        def context_like():
            with warnings.catch_warnings():
                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
                yield
        return context_like()
    else:
        @functools.wraps(f)
        def decorator_like(*args, **kwargs):
            with warnings.catch_warnings():
                warnings.filterwarnings(action="ignore", category=DeprecationWarning)
                return f(*args, **kwargs)
        return decorator_like


def extend(cls):
    def decorator(f):
        if isinstance(f, property):
            name = f.fget.__name__
        else:
            name = f.__name__
        setattr(cls, name, f)
    return decorator


def get_linter_options(filename):
    first_line = linecache.getline(filename, 1)
    if first_line:
        match = re.match(r"^#\s*amaranth:\s*((?:\w+=\w+\s*)(?:,\s*\w+=\w+\s*)*)\n$", first_line)
        if match:
            return dict(map(lambda s: s.strip().split("=", 2), match.group(1).split(",")))
    return dict()


def get_linter_option(filename, name, type, default):
    options = get_linter_options(filename)
    if name not in options:
        return default

    option = options[name]
    if type is bool:
        if option in ("1", "yes", "enable"):
            return True
        if option in ("0", "no", "disable"):
            return False
        return default
    if type is int:
        try:
            return int(option, 0)
        except ValueError:
            return default
    assert False


# amaranth/asserts.py

from .hdl.ast import AnyConst, AnySeq, Assert, Assume, Cover
from .hdl.ast import Past, Stable, Rose, Fell, Initial


# amaranth/cli.py

import argparse

from .hdl.ir import Fragment
from .back import rtlil, cxxrtl, verilog
from .sim import Simulator


__all__ = ["main"]


def main_parser(parser=None):
    if parser is None:
        parser = argparse.ArgumentParser()

    p_action = parser.add_subparsers(dest="action")

    p_generate = p_action.add_parser("generate",
        help="generate RTLIL, Verilog or CXXRTL from the design")
    p_generate.add_argument("-t", "--type", dest="generate_type",
        metavar="LANGUAGE", choices=["il", "cc", "v"],
        help="generate LANGUAGE (il for RTLIL, v for Verilog, cc for CXXRTL; default: file extension of FILE, if given)")
    p_generate.add_argument("--no-src", dest="emit_src", default=True, action="store_false",
        help="suppress generation of source location attributes")
    p_generate.add_argument("generate_file",
        metavar="FILE", type=argparse.FileType("w"), nargs="?",
        help="write generated code to FILE")

    p_simulate = p_action.add_parser(
        "simulate", help="simulate the design")
    p_simulate.add_argument("-v", "--vcd-file",
        metavar="VCD-FILE", type=argparse.FileType("w"),
        help="write execution trace to VCD-FILE")
    p_simulate.add_argument("-w", "--gtkw-file",
        metavar="GTKW-FILE", type=argparse.FileType("w"),
        help="write GTKWave configuration to GTKW-FILE")
    p_simulate.add_argument("-p", "--period", dest="sync_period",
        metavar="TIME", type=float, default=1e-6,
        help="set 'sync' clock domain period to TIME (default: %(default)s)")
    p_simulate.add_argument("-c", "--clocks", dest="sync_clocks",
        metavar="COUNT", type=int, required=True,
        help="simulate for COUNT 'sync' clock periods")

    return parser


def main_runner(parser, args, design, platform=None, name="top", ports=()):
    if args.action == "generate":
        fragment = Fragment.get(design, platform)
        generate_type = args.generate_type
        if generate_type is None and args.generate_file:
            if args.generate_file.name.endswith(".il"):
                generate_type = "il"
            if args.generate_file.name.endswith(".cc"):
                generate_type = "cc"
            if args.generate_file.name.endswith(".v"):
                generate_type = "v"
        if generate_type is None:
            parser.error("Unable to auto-detect language, specify explicitly with -t/--type")
        if generate_type == "il":
            output = rtlil.convert(fragment, name=name, ports=ports, emit_src=args.emit_src)
        if generate_type == "cc":
            output = cxxrtl.convert(fragment, name=name, ports=ports, emit_src=args.emit_src)
        if generate_type == "v":
            output = verilog.convert(fragment, name=name, ports=ports, emit_src=args.emit_src)
        if args.generate_file:
            args.generate_file.write(output)
        else:
            print(output)

    if args.action == "simulate":
        fragment = Fragment.get(design, platform)
        sim = Simulator(fragment)
        sim.add_clock(args.sync_period)
        with sim.write_vcd(vcd_file=args.vcd_file, gtkw_file=args.gtkw_file, traces=ports):
            sim.run_until(args.sync_period * args.sync_clocks, run_passive=True)


def main(*args, **kwargs):
    parser = main_parser()
    main_runner(parser, parser.parse_args(), *args, **kwargs)


# amaranth/rpc.py

import sys
import json
import argparse
import importlib

from .hdl import Signal, Record, Elaboratable
from .back import rtlil


__all__ = ["main"]


def _collect_modules(names):
    modules = {}
    for name in names:
        py_module_name, py_class_name = name.rsplit(".", 1)
        py_module = importlib.import_module(py_module_name)
        if py_class_name == "*":
            for py_class_name in py_module.__all__:
                py_class = py_module.__dict__[py_class_name]
                if not issubclass(py_class, Elaboratable):
                    continue
                modules["{}.{}".format(py_module_name, py_class_name)] = py_class
        else:
            py_class = py_module.__dict__[py_class_name]
            if not isinstance(py_class, type) or not issubclass(py_class, Elaboratable):
                raise TypeError("{}.{} is not a class inheriting from Elaboratable"
                                .format(py_module_name, py_class_name))
            modules[name] = py_class
    return modules


def _serve_yosys(modules):
    while True:
        request_json = sys.stdin.readline()
        if not request_json: break
        request = json.loads(request_json)

        if request["method"] == "modules":
            response = {"modules": list(modules.keys())}

        elif request["method"] == "derive":
            module_name = request["module"]

            args, kwargs = [], {}
            for parameter_name, parameter in request["parameters"].items():
                if parameter["type"] == "unsigned":
                    parameter_value = int(parameter["value"], 2)
                elif parameter["type"] == "signed":
                    width = len(parameter["value"])
                    parameter_value = int(parameter["value"], 2)
                    if parameter_value & (1 << (width - 1)):
                        parameter_value = -((1 << width) - parameter_value)
                elif parameter["type"] == "string":
                    parameter_value = parameter["value"]
                elif parameter["type"] == "real":
                    parameter_value = float(parameter["value"])
                else:
                    raise NotImplementedError("Unrecognized parameter type {}"
                                              .format(parameter_name))
                if parameter_name.startswith("$"):
                    index = int(parameter_name[1:])
                    while len(args) < index:
                        args.append(None)
                    args[index] = parameter_value
                if parameter_name.startswith("\\"):
                    kwargs[parameter_name[1:]] = parameter_value

            try:
                elaboratable = modules[module_name](*args, **kwargs)
                ports = []
                # By convention, any public attribute that is a Signal or a Record is
                # considered a port.
                for port_name, port in vars(elaboratable).items():
                    if not port_name.startswith("_") and isinstance(port, (Signal, Record)):
                        ports += port._lhs_signals()
                rtlil_text = rtlil.convert(elaboratable, name=module_name, ports=ports)
                response = {"frontend": "ilang", "source": rtlil_text}
            except Exception as error:
                response = {"error": "{}: {}".format(type(error).__name__, str(error))}

        else:
            return {"error": "Unrecognized method {!r}".format(request["method"])}

        sys.stdout.write(json.dumps(response))
        sys.stdout.write("\n")
        sys.stdout.flush()


def main():
    parser = argparse.ArgumentParser(description=r"""
    The Amaranth RPC server allows a HDL synthesis program to request an Amaranth module to
    be elaborated on demand using the parameters it provides. For example, using Yosys together
    with the Amaranth RPC server allows instantiating parametric Amaranth modules directly
    from Verilog.
    """)
    def add_modules_arg(parser):
        parser.add_argument("modules", metavar="MODULE", type=str, nargs="+",
            help="import and provide MODULES")
    protocols = parser.add_subparsers(metavar="PROTOCOL", dest="protocol", required=True)
    protocol_yosys = protocols.add_parser("yosys", help="use Yosys JSON-based RPC protocol")
    add_modules_arg(protocol_yosys)

    args = parser.parse_args()
    modules = _collect_modules(args.modules)
    if args.protocol == "yosys":
        _serve_yosys(modules)


if __name__ == "__main__":
    main()


# amaranth/tracer.py

import sys
from opcode import opname


__all__ = ["NameNotFound", "get_var_name", "get_src_loc"]


class NameNotFound(Exception):
    pass


_raise_exception = object()


def get_var_name(depth=2, default=_raise_exception):
    frame = sys._getframe(depth)
    code = frame.f_code
    call_index = frame.f_lasti
    while call_index > 0 and opname[code.co_code[call_index]] == "CACHE":
        call_index -= 2
    while True:
        call_opc = opname[code.co_code[call_index]]
        if call_opc in ("EXTENDED_ARG",):
            call_index += 2
        else:
            break
    if call_opc not in ("CALL_FUNCTION", "CALL_FUNCTION_KW", "CALL_FUNCTION_EX",
                        "CALL_METHOD", "CALL"):
        if default is _raise_exception:
            raise NameNotFound
        else:
            return default

    index = call_index + 2
    imm = 0
    while True:
        opc = opname[code.co_code[index]]
        if opc == 'EXTENDED_ARG':
            imm |= int(code.co_code[index + 1])
            imm <<= 8
            index += 2
        elif opc in ("STORE_NAME", "STORE_ATTR"):
            imm |= int(code.co_code[index + 1])
            return code.co_names[imm]
        elif opc == "STORE_FAST":
            imm |= int(code.co_code[index + 1])
            if sys.version_info >= (3, 11):
                return code._varname_from_oparg(imm)
            else:
                return code.co_varnames[imm]
        elif opc == "STORE_DEREF":
            imm |= int(code.co_code[index + 1])
            if sys.version_info >= (3, 11):
                return code._varname_from_oparg(imm)
            else:
                if imm < len(code.co_cellvars):
                    return code.co_cellvars[imm]
                else:
                    return code.co_freevars[imm - len(code.co_cellvars)]
        elif opc in ("LOAD_GLOBAL", "LOAD_NAME", "LOAD_ATTR", "LOAD_FAST", "LOAD_DEREF",
                     "DUP_TOP", "BUILD_LIST", "CACHE", "COPY"):
            imm = 0
            index += 2
        else:
            if default is _raise_exception:
                raise NameNotFound
            else:
                return default


def get_src_loc(src_loc_at=0):
    # n-th  frame: get_src_loc()
    # n-1th frame: caller of get_src_loc() (usually constructor)
    # n-2th frame: caller of caller (usually user code)
    frame = sys._getframe(2 + src_loc_at)
    return (frame.f_code.co_filename, frame.f_lineno)


# amaranth/utils.py

__all__ = ["log2_int", "bits_for"]


def log2_int(n, need_pow2=True):
    if n == 0:
        return 0
    r = (n - 1).bit_length()
    if need_pow2 and (1 << r) != n:
        raise ValueError("{} is not a power of 2".format(n))
    return r


def bits_for(n, require_sign_bit=False):
    if n > 0:
        r = log2_int(n + 1, False)
    else:
        require_sign_bit = True
        r = log2_int(-n, False)
    if require_sign_bit:
        r += 1
    return r


# amaranth/vendor/gowin.py

from abc import abstractproperty
from fractions import Fraction
import re

from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *

# Acknowledgments:
#   Parts of this file originate from https://github.com/tcjie/Gowin


__all__ = ["GowinPlatform"]


class GowinPlatform(TemplatedPlatform):
    """
    .. rubric:: Apicula toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-gowin``
        * ``gowin_pack``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_APICULA``, if present.

    Build products:
        * ``{{name}}.fs``: binary bitstream.

    .. rubric:: Gowin toolchain

    Required tools:
        * ``gw_sh``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_GOWIN``, if present.

    Build products:
        * ``{{name}}.fs``: binary bitstream.
    """

    toolchain = None # selected when creating platform

    part   = abstractproperty()
    family = abstractproperty()

    def parse_part(self):
        # These regular expressions match all >900 parts of Gowin device_info.csv
        reg_series    = r"(GW[12]{1}[AN]{1}[EFNRSZ]{0,3})-"
        reg_voltage   = r"(ZV|EV|LV|LX|UV|UX)"
        reg_size      = r"(1|2|4|9|18|55)"
        reg_subseries = r"(?:(B|C|S|X|P5)?)"
        reg_package   = r"((?:PG|UG|EQ|LQ|MG|M|QN|CS|FN)(?:\d+)(?:P?)(?:A|E|M|CF|C|D|G|H|F|S|T|U|X)?)"
        reg_speed     = r"((?:C\d{1}/I\d{1})|ES|A\d{1}|I\d{1})"

        match = re.match(reg_series+reg_voltage+reg_size+reg_subseries+reg_package+reg_speed+"$",
                         self.part)
        if not match:
            raise ValueError("Supplied part name is invalid")

        self.series    = match.group(1)
        self.voltage   = match.group(2)
        self.size      = match.group(3)
        self.subseries = match.group(4) or ""
        self.package   = match.group(5)
        self.speed     = match.group(6)

        match = re.match(reg_series+reg_size+reg_subseries+"$", self.family)
        if not match:
            raise ValueError("Supplied device family name is invalid")

        self.series_f    = match.group(1)
        self.size_f      = match.group(2)
        self.subseries_f = match.group(3) or ""

        # subseries_f is usually more reliable than subseries.

        if self.series != self.series_f:
            raise ValueError("Series extracted from supplied part name does not match "
                             "supplied family series")
        if self.size != self.size_f:
            raise ValueError("Size extracted from supplied part name does not match "
                             "supplied family size")

    # _chipdb_device is tied to available chipdb-*.bin files of nextpnr-gowin
    @property
    def _chipdb_device(self):
        # GW1NR series does not have its own chipdb file, but works with GW1N
        if self.series == "GW1NR":
            return "GW1N-{}{}".format(self.size, self.subseries_f)
        return self.family

    _dev_osc_mapping = {
       "GW1N-1" : "OSCH",
       "GW1N-1P5" : "OSCO",
       "GW1N-1P5B" : "OSCO",
       "GW1N-1S" : "OSCH",
       "GW1N-2" : "OSCO",
       "GW1N-2B" : "OSCO",
       "GW1N-4" : "OSC",
       "GW1N-4B" : "OSC",
       "GW1N-9" : "OSC",
       "GW1N-9C" : "OSC",
       "GW1NR-1" : "OSCH",
       "GW1NR-2" : "OSCO",
       "GW1NR-2B" : "OSCO",
       "GW1NR-4" : "OSC",
       "GW1NR-4B" : "OSC",
       "GW1NR-9" : "OSC",
       "GW1NR-9C" : "OSC",
       "GW1NRF-4B" : "OSC",
       "GW1NS-2" : "OSCF",
       "GW1NS-2C" : "OSCF",
       "GW1NS-4" : "OSCZ",
       "GW1NS-4C" : "OSCZ",
       "GW1NSE-2C" : "OSCF",
       "GW1NSER-4C" : "OSCZ",
       "GW1NSR-2" : "OSCF",
       "GW1NSR-2C" : "OSCF",
       "GW1NSR-4" : "OSCZ",
       "GW1NSR-4C" : "OSCZ",
       "GW1NZ-1" : "OSCZ",
       "GW1NZ-1C" : "OSCZ",
       "GW2A-18" : "OSC",
       "GW2A-18C" : "OSC",
       "GW2A-55" : "OSC",
       "GW2A-55C" : "OSC",
       "GW2AN-18X" : "OSCW",
       "GW2AN-55C" : "OSC",
       "GW2AN-9X" : "OSCW",
       "GW2ANR-18C" : "OSC",
       "GW2AR-18" : "OSC",
       "GW2AR-18C" : "OSC"
    }

    @property
    def _osc_type(self):
        if self.family in self._dev_osc_mapping:
            return self._dev_osc_mapping[self.family]
        raise NotImplementedError("Device family {} does not have an assigned oscillator type"
                                  .format(self.family))

    @property
    def _osc_base_freq(self):
        osc = self._osc_type
        if osc == "OSC":
            if self.speed == 4 and self.subseries_f in ("B", "D"):
                return 210_000_000
            else:
                return 250_000_000
        elif osc in ("OSCZ", "OSCO"):
            if self.series == "GW1NSR" and self.speed == "C7/I6":
                return 260_000_000
            else:
                return 250_000_000
        elif osc in ("OSCF", "OSCH"):
            return 240_000_000
        elif osc == "OSCW":
            return 200_000_000
        else:
            assert False

    @property
    def _osc_div(self):
        div_min  = 2
        div_max  = 128
        div_step = 2
        div_frac = Fraction(self._osc_base_freq, self.osc_frequency)

        if div_frac.denominator != 1 or div_frac not in range(div_min, div_max, div_step):
            raise ValueError(
                "On-chip oscillator frequency (platform.osc_frequency) must be chosen such that "
                "the oscillator divider, calculated as ({}/{}), is an integer between {} and {} in "
                "steps of {}"
                .format(div_frac.numerator, div_frac.denominator, div_min, div_max, div_step))

        return div_frac.numerator

    # Common templates

    _common_file_templates = {
        "{{name}}.cst": r"""
            // {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                IO_LOC "{{port_name}}" {{pin_name}};
                {% for attr_name, attr_value in attrs.items() -%}
                    IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}};
                {% endfor %}
            {% endfor %}
        """,
    }

    # Apicula templates

    _apicula_required_tools = [
        "yosys",
        "nextpnr-gowin",
        "gowin_pack"
    ]
    _apicula_file_templates = {
        **TemplatedPlatform.build_script_templates,
        **_common_file_templates,
        "{{name}}.il": r"""
            # {{autogenerated}}
            {{emit_rtlil()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.ys": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v") -%}
                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".il") -%}
                read_ilang {{file}}
            {% endfor %}
            read_ilang {{name}}.il
            delete w:$verilog_initial_trigger
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.syn.json
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
        """,
    }
    _apicula_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            {{quiet("-q")}}
            {{get_override("yosys_opts")|options}}
            -l {{name}}.rpt
            {{name}}.ys
        """,
        r"""
        {{invoke_tool("nextpnr-gowin")}}
            {{quiet("--quiet")}}
            {{get_override("nextpnr_opts")|options}}
            --log {{name}}.tim
            --device {{platform.part}}
            --family {{platform._chipdb_device}}
            --json {{name}}.syn.json
            --cst {{name}}.cst
            --write {{name}}.pnr.json
        """,
        r"""
        {{invoke_tool("gowin_pack")}}
            -d {{platform._chipdb_device}}
            -o {{name}}.fs
            {{get_override("gowin_pack_opts")|options}}
            {{name}}.pnr.json
        """
    ]

    # Vendor toolchain templates

    _gowin_required_tools = ["gw_sh"]
    _gowin_file_templates = {
        **TemplatedPlatform.build_script_templates,
        **_common_file_templates,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.tcl": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%}
                add_file {{file}}
            {% endfor %}
            add_file -type verilog {{name}}.v
            add_file -type cst {{name}}.cst
            add_file -type sdc {{name}}.sdc
            set_device -name {{platform.family}} {{platform.part}}
            set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1
            {{get_override("add_options")|default("# (add_options placeholder)")}}
            run all
            file delete -force {{name}}.fs
            file copy -force impl/pnr/project.fs {{name}}.fs
        """,
        "{{name}}.sdc": r"""
        // {{autogenerated}}
        {% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%}
            create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}}  [get_ports {{port_signal.name|tcl_escape}}]
        {% endfor %}
        {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
    }
    _gowin_command_templates = [
        r"""
        {{invoke_tool("gw_sh")}}
            {{name}}.tcl
        """
    ]

    def __init__(self, *, toolchain="Apicula"):
        super().__init__()

        assert toolchain in ("Apicula", "Gowin")
        self.toolchain = toolchain

        self.parse_part()

    @property
    def required_tools(self):
        if self.toolchain == "Apicula":
            return self._apicula_required_tools
        elif self.toolchain == "Gowin":
            return self._gowin_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "Apicula":
            return self._apicula_file_templates
        elif self.toolchain == "Gowin":
            return self._gowin_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "Apicula":
            return self._apicula_command_templates
        elif self.toolchain == "Gowin":
            return self._gowin_command_templates
        assert False

    def add_clock_constraint(self, clock, frequency):
        super().add_clock_constraint(clock, frequency)
        clock.attrs["keep"] = "true"

    @property
    def default_clk_constraint(self):
        if self.default_clk == "OSC":
            if not hasattr(self, "osc_frequency"):
                raise AttributeError(
                    "Using the on-chip oscillator as the default clock source requires "
                    "the platform.osc_frequency attribute to be set")
            return Clock(self.osc_frequency)

        # Use the defined Clock resource.
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        if name == "sync" and self.default_clk is not None:
            m = Module()

            if self.default_clk == "OSC":
                clk_i = Signal()
                if self._osc_type == "OSCZ":
                    m.submodules += Instance(self._osc_type,
                                             p_FREQ_DIV=self._osc_div,
                                             i_OSCEN=Const(1),
                                             o_OSCOUT=clk_i)
                elif self._osc_type == "OSCO":
                    # TODO: Make use of regulator configurable
                    m.submodules += Instance(self._osc_type,
                                             p_REGULATOR_EN=Const(1),
                                             p_FREQ_DIV=self._osc_div,
                                             i_OSCEN=Const(1),
                                             o_OSCOUT=clk_i)
                elif self._osc_type == "OSCF":
                    m.submodules += Instance(self._osc_type,
                                             p_FREQ_DIV=self._osc_div,
                                             o_OSCOUT30M=None,
                                             o_OSCOUT=clk_i)
                else:
                    m.submodules += Instance(self._osc_type,
                                             p_FREQ_DIV=self._osc_div,
                                             o_OSCOUT=clk_i)

            else:
                clk_i = self.request(self.default_clk).i

            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
            m.domains += ClockDomain("sync")
            m.d.comb += ClockSignal("sync").eq(clk_i)

            return m

    def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False):

        def get_ireg(clk,d,q):
            for bit in range(len(q)):
                m.submodules += Instance("DFF",
                    i_CLK=clk,
                    i_D=d[bit],
                    o_Q=q[bit],
                )

        def get_oreg(clk,d,q):
            for bit in range(len(q)):
                m.submodules += Instance("DFF",
                    i_CLK=clk,
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_iddr(clk,d,q0,q1):
            for bit in range(len(d)):
                m.submodules += Instance("IDDR",
                    i_CLK=clk,
                    i_D=d[bit],
                    o_Q0=q0[bit],
                    o_Q1=q1[bit]
                )

        def get_oddr(clk,d0,d1,q):
            for bit in range(len(q)):
                m.submodules += Instance("ODDR",
                    p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
                    i_CLK=clk,
                    i_D0=d0[bit],
                    i_D1=d1[bit],
                    o_Q0=q[bit]
                )

        def get_oeddr(clk,d0,d1,tx,q0,q1):
            for bit in range(len(q0)):
                m.submodules += Instance("ODDR",
                    p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
                    i_CLK=clk,
                    i_D0=d0[bit],
                    i_D1=d1[bit],
                    i_TX=tx,
                    o_Q0=q0[bit],
                    o_Q1=q1
                )

        def get_ineg(y, invert):
            if invert:
                a = Signal.like(y, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return a
            else:
                return y

        def get_oneg(a, invert):
            if invert:
                y = Signal.like(a, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return y
            else:
                return a


        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i = get_ineg(pin.i, i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o = get_oneg(pin.o, o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)

        i = o = t = None

        if "i" in pin.dir:
            i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
        if "o" in pin.dir:
            o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
        if pin.dir in ("oe", "io"):
            t = Signal(1,         name="{}_xdr_t".format(pin.name))

        if pin.xdr == 0:
            if "i" in pin.dir:
                i = pin_i
            if "o" in pin.dir:
                o = pin_o
            if pin.dir in ("oe", "io"):
                t = ~pin.oe
        elif pin.xdr == 1:
            if "i" in pin.dir:
                get_ireg(pin.i_clk, i, pin_i)
            if "o" in pin.dir:
                get_oreg(pin.o_clk, pin_o, o)
            if pin.dir in ("oe", "io"):
                get_oreg(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 2:
            if "i" in pin.dir:
                get_iddr(pin.i_clk, i, pin_i0, pin_i1)
            if pin.dir in ("o",):
                get_oddr(pin.o_clk, pin_o0, pin_o1, o)
            if pin.dir in ("oe", "io"):
                get_oeddr(pin.o_clk, pin_o0, pin_o1, ~pin.oe, o, t)
        else:
            assert False

        return (i, o, t)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF",
                i_I=port.io[bit],
                o_O=i[bit]
            )
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, port.io, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF",
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF",
                i_OEN=t,
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF",
                i_OEN=t,
                i_I=o[bit],
                o_O=i[bit],
                io_IO=port.io[bit]
            )
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(pin.wodth):
            m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF",
                i_I=port.p[bit],
                i_IB=port.n[bit],
                o_O=i[bit]
            )
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF",
                i_I=o[bit],
                o_O=port.p[bit],
                o_OB=port.n[bit],
            )
        return m

    def get_diff_tristate(self, pin, port, attrs, invert):
        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF",
                i_OEN=t,
                i_I=o[bit],
                o_O=port.p[bit],
                o_OB=port.n[bit]
            )
        return m

    def get_diff_input_output(self, pin, port, atttr, invert):
        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF",
                i_OEN=t,
                i_I=o[bit],
                o_O=i[bit],
                io_IO=port.p[bit],
                io_IOB=port.n[bit]
            )
        return m


# amaranth/vendor/intel.py

from abc import abstractmethod

from ..hdl import *
from ..build import *


__all__ = ["IntelPlatform"]


class IntelPlatform(TemplatedPlatform):
    """
    .. rubric:: Quartus toolchain

    Required tools:
        * ``quartus_map``
        * ``quartus_fit``
        * ``quartus_asm``
        * ``quartus_sta``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_QUARTUS``, if present.

    Available overrides:
        * ``add_settings``: inserts commands at the end of the QSF file.
        * ``add_constraints``: inserts commands at the end of the SDC file.
        * ``nproc``: sets the number of cores used by all tools.
        * ``quartus_map_opts``: adds extra options for ``quartus_map``.
        * ``quartus_fit_opts``: adds extra options for ``quartus_fit``.
        * ``quartus_asm_opts``: adds extra options for ``quartus_asm``.
        * ``quartus_sta_opts``: adds extra options for ``quartus_sta``.

    Build products:
        * ``*.rpt``: toolchain reports.
        * ``{{name}}.sof``: bitstream as SRAM object file.
        * ``{{name}}.rbf``: bitstream as raw binary file.


    .. rubric:: Mistral toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-mistral``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_MISTRAL``, if present.

        * ``verbose``: enables logging of informational messages to standard error.
        * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
        * ``synth_opts``: adds options for ``synth_intel_alm`` Yosys command.
        * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
        * ``script_after_synth``: inserts commands after ``synth_intel_alm`` in Yosys script.
        * ``yosys_opts``: adds extra options for ``yosys``.
        * ``nextpnr_opts``: adds extra options for ``nextpnr-mistral``.
    """

    toolchain = None # selected when creating platform

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))
    speed   = property(abstractmethod(lambda: None))
    suffix  = ""

    # Quartus templates

    quartus_suppressed_warnings = [
        10264,  # All case item expressions in this case statement are onehot
        10270,  # Incomplete Verilog case statement has no default case item
        10335,  # Unrecognized synthesis attribute
        10763,  # Verilog case statement has overlapping case item expressions with non-constant or don't care bits
        10935,  # Verilog casex/casez overlaps with a previous casex/vasez item expression
        12125,  # Using design file which is not specified as a design file for the current project, but contains definitions used in project
        18236,  # Number of processors not specified in QSF
        292013, # Feature is only available with a valid subscription license
    ]

    quartus_required_tools = [
        "quartus_map",
        "quartus_fit",
        "quartus_asm",
        "quartus_sta",
    ]

    quartus_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            {% for var in platform._all_toolchain_env_vars %}
            if [ -n "${{var}}" ]; then
                QUARTUS_ROOTDIR=$(dirname $(dirname "${{var}}"))
                # Quartus' qenv.sh does not work with `set -e`.
                . "${{var}}"
            fi
            {% endfor %}
            set -e{{verbose("x")}}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.qsf": r"""
            # {{autogenerated}}
            {% if get_override("nproc") -%}
                set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
            {% endif %}

            {% for file in platform.iter_files(".v") -%}
                set_global_assignment -name VERILOG_FILE {{file|tcl_quote}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_quote}}
            {% endfor %}
            {% for file in platform.iter_files(".vhd", ".vhdl") -%}
                set_global_assignment -name VHDL_FILE {{file|tcl_quote}}
            {% endfor %}
            set_global_assignment -name VERILOG_FILE {{name}}.v
            set_global_assignment -name TOP_LEVEL_ENTITY {{name}}

            set_global_assignment -name DEVICE {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
                {% for key, value in attrs.items() -%}
                    set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
                {% endfor %}
            {% endfor %}

            set_global_assignment -name GENERATE_RBF_FILE ON

            {{get_override("add_settings")|default("# (add_settings placeholder)")}}
        """,
        "{{name}}.sdc": r"""
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_quote}}]
                {% else -%}
                    create_clock -name {{net_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")|tcl_quote}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
        "{{name}}.srf": r"""
            {% for warning in platform.quartus_suppressed_warnings %}
            { "" "" "" "{{name}}.v" {  } {  } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""}
            {% endfor %}
        """,
    }
    quartus_command_templates = [
        r"""
        {{invoke_tool("quartus_map")}}
            {{get_override("quartus_map_opts")|options}}
            --rev={{name}} {{name}}
        """,
        r"""
        {{invoke_tool("quartus_fit")}}
            {{get_override("quartus_fit_opts")|options}}
            --rev={{name}} {{name}}
        """,
        r"""
        {{invoke_tool("quartus_asm")}}
            {{get_override("quartus_asm_opts")|options}}
            --rev={{name}} {{name}}
        """,
        r"""
        {{invoke_tool("quartus_sta")}}
            {{get_override("quartus_sta_opts")|options}}
            --rev={{name}} {{name}}
        """,
    ]


    # Mistral templates

    mistral_required_tools = [
        "yosys",
        "nextpnr-mistral"
    ]
    mistral_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.il": r"""
            # {{autogenerated}}
            {{emit_rtlil()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.ys": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v") -%}
                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".il") -%}
                read_ilang {{file}}
            {% endfor %}
            read_ilang {{name}}.il
            delete w:$verilog_initial_trigger
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_intel_alm {{get_override("synth_opts")|options}} -top {{name}}
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
            write_json {{name}}.json
        """,
        "{{name}}.qsf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
                {% for key, value in attrs.items() -%}
                    set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
                {% endfor %}
            {% endfor %}
        """,

    }
    mistral_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            {{quiet("-q")}}
            {{get_override("yosys_opts")|options}}
            -l {{name}}.rpt
            {{name}}.ys
        """,
        r"""
        {{invoke_tool("nextpnr-mistral")}}
            {{quiet("--quiet")}}
            {{get_override("nextpnr_opts")|options}}
            --log {{name}}.tim
            --device {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
            --json {{name}}.json
            --qsf {{name}}.qsf
            --rbf {{name}}.rbf
        """
    ]

    # Common logic

    def __init__(self, *, toolchain="Quartus"):
        super().__init__()

        assert toolchain in ("Quartus", "Mistral")
        self.toolchain = toolchain

    @property
    def required_tools(self):
        if self.toolchain == "Quartus":
            return self.quartus_required_tools
        if self.toolchain == "Mistral":
            return self.mistral_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "Quartus":
            return self.quartus_file_templates
        if self.toolchain == "Mistral":
            return self.mistral_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "Quartus":
            return self.quartus_command_templates
        if self.toolchain == "Mistral":
            return self.mistral_command_templates
        assert False

    def add_clock_constraint(self, clock, frequency):
        super().add_clock_constraint(clock, frequency)
        clock.attrs["keep"] = "true"

    @property
    def default_clk_constraint(self):
        # Internal high-speed oscillator on Cyclone V devices.
        # It is specified to not be faster than 100MHz, but the actual
        # frequency seems to vary a lot between devices. Measurements
        # of 78 to 84 MHz have been observed.
        if self.default_clk == "cyclonev_oscillator":
            assert self.device.startswith("5C")
            return Clock(100e6)
        # Otherwise, use the defined Clock resource.
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        if name == "sync" and self.default_clk == "cyclonev_oscillator":
            # Use the internal high-speed oscillator for Cyclone V devices
            assert self.device.startswith("5C")
            m = Module()
            m.domains += ClockDomain("sync")
            m.submodules += Instance("cyclonev_oscillator",
                                     i_oscena=Const(1),
                                     o_clkout=ClockSignal("sync"))
            return m
        else:
            return super().create_missing_domain(name)

    # The altiobuf_* and altddio_* primitives are explained in the following Intel documents:
    # * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altiobuf.pdf
    # * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altddio.pdf
    # See also errata mentioned in: https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd11192012_735.html.

    @staticmethod
    def _get_ireg(m, pin, invert):
        def get_ineg(i):
            if invert:
                i_neg = Signal.like(i, name_suffix="_neg")
                m.d.comb += i.eq(~i_neg)
                return i_neg
            else:
                return i

        if pin.xdr == 0:
            return get_ineg(pin.i)
        elif pin.xdr == 1:
            i_sdr = Signal(pin.width, name="{}_i_sdr")
            m.submodules += Instance("$dff",
                p_CLK_POLARITY=1,
                p_WIDTH=pin.width,
                i_CLK=pin.i_clk,
                i_D=i_sdr,
                o_Q=get_ineg(pin.i),
            )
            return i_sdr
        elif pin.xdr == 2:
            i_ddr = Signal(pin.width, name="{}_i_ddr".format(pin.name))
            m.submodules["{}_i_ddr".format(pin.name)] = Instance("altddio_in",
                p_width=pin.width,
                i_datain=i_ddr,
                i_inclock=pin.i_clk,
                o_dataout_h=get_ineg(pin.i0),
                o_dataout_l=get_ineg(pin.i1),
            )
            return i_ddr
        assert False

    @staticmethod
    def _get_oreg(m, pin, invert):
        def get_oneg(o):
            if invert:
                o_neg = Signal.like(o, name_suffix="_neg")
                m.d.comb += o_neg.eq(~o)
                return o_neg
            else:
                return o

        if pin.xdr == 0:
            return get_oneg(pin.o)
        elif pin.xdr == 1:
            o_sdr = Signal(pin.width, name="{}_o_sdr".format(pin.name))
            m.submodules += Instance("$dff",
                p_CLK_POLARITY=1,
                p_WIDTH=pin.width,
                i_CLK=pin.o_clk,
                i_D=get_oneg(pin.o),
                o_Q=o_sdr,
            )
            return o_sdr
        elif pin.xdr == 2:
            o_ddr = Signal(pin.width, name="{}_o_ddr".format(pin.name))
            m.submodules["{}_o_ddr".format(pin.name)] = Instance("altddio_out",
                p_width=pin.width,
                o_dataout=o_ddr,
                i_outclock=pin.o_clk,
                i_datain_h=get_oneg(pin.o0),
                i_datain_l=get_oneg(pin.o1),
            )
            return o_ddr
        assert False

    @staticmethod
    def _get_oereg(m, pin):
        # altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide.
        if pin.xdr == 0:
            return pin.oe.replicate(pin.width)
        elif pin.xdr in (1, 2):
            oe_reg = Signal(pin.width, name="{}_oe_reg".format(pin.name))
            oe_reg.attrs["useioff"] = "1"
            m.submodules += Instance("$dff",
                p_CLK_POLARITY=1,
                p_WIDTH=pin.width,
                i_CLK=pin.o_clk,
                i_D=pin.oe,
                o_Q=oe_reg,
            )
            return oe_reg
        assert False

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_in",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="FALSE",
            i_datain=port.io,
            o_dataout=self._get_ireg(m, pin, invert)
        )
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_out",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="FALSE",
            p_use_oe="FALSE",
            i_datain=self._get_oreg(m, pin, invert),
            o_dataout=port.io,
        )
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_out",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="FALSE",
            p_use_oe="TRUE",
            i_datain=self._get_oreg(m, pin, invert),
            o_dataout=port.io,
            i_oe=self._get_oereg(m, pin)
        )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_bidir",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="FALSE",
            i_datain=self._get_oreg(m, pin, invert),
            io_dataio=port.io,
            o_dataout=self._get_ireg(m, pin, invert),
            i_oe=self._get_oereg(m, pin),
        )
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.p.attrs["useioff"] = 1
            port.n.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_in",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="TRUE",
            i_datain=port.p,
            i_datain_b=port.n,
            o_dataout=self._get_ireg(m, pin, invert)
        )
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.p.attrs["useioff"] = 1
            port.n.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_out",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="TRUE",
            p_use_oe="FALSE",
            i_datain=self._get_oreg(m, pin, invert),
            o_dataout=port.p,
            o_dataout_b=port.n,
        )
        return m

    def get_diff_tristate(self, pin, port, attrs, invert):
        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.p.attrs["useioff"] = 1
            port.n.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_out",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="TRUE",
            p_use_oe="TRUE",
            i_datain=self._get_oreg(m, pin, invert),
            o_dataout=port.p,
            o_dataout_b=port.n,
            i_oe=self._get_oereg(m, pin),
        )
        return m

    def get_diff_input_output(self, pin, port, attrs, invert):
        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        if pin.xdr == 1:
            port.p.attrs["useioff"] = 1
            port.n.attrs["useioff"] = 1

        m = Module()
        m.submodules[pin.name] = Instance("altiobuf_bidir",
            p_enable_bus_hold="FALSE",
            p_number_of_channels=pin.width,
            p_use_differential_mode="TRUE",
            i_datain=self._get_oreg(m, pin, invert),
            io_dataio=port.p,
            io_dataio_b=port.n,
            o_dataout=self._get_ireg(m, pin, invert),
            i_oe=self._get_oereg(m, pin),
        )
        return m

    # The altera_std_synchronizer{,_bundle} megafunctions embed SDC constraints that mark false
    # paths, so use them instead of our default implementation.

    def get_ff_sync(self, ff_sync):
        return Instance("altera_std_synchronizer_bundle",
            p_width=len(ff_sync.i),
            p_depth=ff_sync._stages,
            i_clk=ClockSignal(ff_sync._o_domain),
            i_reset_n=Const(1),
            i_din=ff_sync.i,
            o_dout=ff_sync.o,
        )

    def get_async_ff_sync(self, async_ff_sync):
        m = Module()
        sync_output = Signal()
        if async_ff_sync._edge == "pos":
            m.submodules += Instance("altera_std_synchronizer",
                p_depth=async_ff_sync._stages,
                i_clk=ClockSignal(async_ff_sync._o_domain),
                i_reset_n=~async_ff_sync.i,
                i_din=Const(1),
                o_dout=sync_output,
            )
        else:
            m.submodules += Instance("altera_std_synchronizer",
                p_depth=async_ff_sync._stages,
                i_clk=ClockSignal(async_ff_sync._o_domain),
                i_reset_n=async_ff_sync.i,
                i_din=Const(1),
                o_dout=sync_output,
            )
        m.d.comb += async_ff_sync.o.eq(~sync_output)
        return m


# amaranth/vendor/lattice_ecp5.py

from abc import abstractmethod

from ..hdl import *
from ..build import *


__all__ = ["LatticeECP5Platform"]


class LatticeECP5Platform(TemplatedPlatform):
    """
    .. rubric:: Trellis toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-ecp5``
        * ``ecppack``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_TRELLIS``, if present.

    Available overrides:
        * ``verbose``: enables logging of informational messages to standard error.
        * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
        * ``synth_opts``: adds options for ``synth_ecp5`` Yosys command.
        * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
        * ``script_after_synth``: inserts commands after ``synth_ecp5`` in Yosys script.
        * ``yosys_opts``: adds extra options for ``yosys``.
        * ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``.
        * ``ecppack_opts``: adds extra options for ``ecppack``.
        * ``add_preferences``: inserts commands at the end of the LPF file.

    Build products:
        * ``{{name}}.rpt``: Yosys log.
        * ``{{name}}.json``: synthesized RTL.
        * ``{{name}}.tim``: nextpnr log.
        * ``{{name}}.config``: ASCII bitstream.
        * ``{{name}}.bit``: binary bitstream.
        * ``{{name}}.svf``: JTAG programming vector.

    .. rubric:: Diamond toolchain

    Required tools:
        * ``pnmainc``
        * ``ddtcmd``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
    itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
    for instance) is known to work::

        @echo off
        set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%

    Available overrides:
        * ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
        * ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
        * ``add_preferences``: inserts commands at the end of the LPF file.
        * ``add_constraints``: inserts commands at the end of the XDC file.

    Build products:
        * ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
        * ``{{name}}.bit``: binary bitstream.
        * ``{{name}}.svf``: JTAG programming vector.
    """

    toolchain = None # selected when creating platform

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))
    speed   = property(abstractmethod(lambda: None))
    grade   = "C" # [C]ommercial, [I]ndustrial

    # Trellis templates

    _nextpnr_device_options = {
        "LFE5U-12F":    "--12k",
        "LFE5U-25F":    "--25k",
        "LFE5U-45F":    "--45k",
        "LFE5U-85F":    "--85k",
        "LFE5UM-25F":   "--um-25k",
        "LFE5UM-45F":   "--um-45k",
        "LFE5UM-85F":   "--um-85k",
        "LFE5UM5G-25F": "--um5g-25k",
        "LFE5UM5G-45F": "--um5g-45k",
        "LFE5UM5G-85F": "--um5g-85k",
    }
    _nextpnr_package_options = {
        "BG256": "caBGA256",
        "MG285": "csfBGA285",
        "BG381": "caBGA381",
        "BG554": "caBGA554",
        "BG756": "caBGA756",
    }

    _trellis_required_tools = [
        "yosys",
        "nextpnr-ecp5",
        "ecppack"
    ]
    _trellis_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.il": r"""
            # {{autogenerated}}
            {{emit_rtlil()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.ys": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v") -%}
                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".il") -%}
                read_ilang {{file}}
            {% endfor %}
            read_ilang {{name}}.il
            delete w:$verilog_initial_trigger
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_ecp5 {{get_override("synth_opts")|options}} -top {{name}}
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
            write_json {{name}}.json
        """,
        "{{name}}.lpf": r"""
            # {{autogenerated}}
            BLOCK ASYNCPATHS;
            BLOCK RESETPATHS;
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
                {% if attrs -%}
                IOBUF PORT "{{port_name}}"
                    {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
                {% endif %}
            {% endfor %}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    FREQUENCY PORT "{{port_signal.name}}" {{frequency}} HZ;
                {% else -%}
                    FREQUENCY NET "{{net_signal|hierarchy(".")}}" {{frequency}} HZ;
                {% endif %}
            {% endfor %}
            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
        """
    }
    _trellis_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            {{quiet("-q")}}
            {{get_override("yosys_opts")|options}}
            -l {{name}}.rpt
            {{name}}.ys
        """,
        r"""
        {{invoke_tool("nextpnr-ecp5")}}
            {{quiet("--quiet")}}
            {{get_override("nextpnr_opts")|options}}
            --log {{name}}.tim
            {{platform._nextpnr_device_options[platform.device]}}
            --package {{platform._nextpnr_package_options[platform.package]|upper}}
            --speed {{platform.speed}}
            --json {{name}}.json
            --lpf {{name}}.lpf
            --textcfg {{name}}.config
        """,
        r"""
        {{invoke_tool("ecppack")}}
            {{verbose("--verbose")}}
            {{get_override("ecppack_opts")|options}}
            --input {{name}}.config
            --bit {{name}}.bit
            --svf {{name}}.svf
        """
    ]

    # Diamond templates

    _diamond_required_tools = [
        "pnmainc",
        "ddtcmd"
    ]
    _diamond_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
            {% for var in platform._all_toolchain_env_vars %}
            if [ -n "${{var}}" ]; then
                bindir=$(dirname "${{var}}")
                . "${{var}}"
            fi
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.tcl": r"""
            prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
                -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
                -lpf {{name}}.lpf \
                -synthesis synplify
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
                prj_src add {{file|tcl_escape}}
            {% endfor %}
            prj_src add {{name}}.v
            prj_impl option top {{name}}
            prj_src add {{name}}.sdc
            {{get_override("script_project")|default("# (script_project placeholder)")}}
            prj_project save
            prj_run Synthesis -impl impl
            prj_run Translate -impl impl
            prj_run Map -impl impl
            prj_run PAR -impl impl
            prj_run Export -impl impl -task Bitgen
            {{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
        """,
        "{{name}}.lpf": r"""
            # {{autogenerated}}
            BLOCK ASYNCPATHS;
            BLOCK RESETPATHS;
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
                {% if attrs -%}
                IOBUF PORT "{{port_name}}"
                    {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
                {% endif %}
            {% endfor %}
            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
        """,
        "{{name}}.sdc": r"""
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
                {% else -%}
                    create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
    }
    _diamond_command_templates = [
        # These don't have any usable command-line option overrides.
        r"""
        {{invoke_tool("pnmainc")}}
            {{name}}.tcl
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -bit
            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -svfsingle -revd -op "Fast Program"
            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}.svf
        """,
    ]

    # Common logic

    def __init__(self, *, toolchain="Trellis"):
        super().__init__()

        assert toolchain in ("Trellis", "Diamond")
        self.toolchain = toolchain

    @property
    def required_tools(self):
        if self.toolchain == "Trellis":
            return self._trellis_required_tools
        if self.toolchain == "Diamond":
            return self._diamond_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "Trellis":
            return self._trellis_file_templates
        if self.toolchain == "Diamond":
            return self._diamond_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "Trellis":
            return self._trellis_command_templates
        if self.toolchain == "Diamond":
            return self._diamond_command_templates
        assert False

    @property
    def default_clk_constraint(self):
        if self.default_clk == "OSCG":
            return Clock(310e6 / self.oscg_div)
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        # Lattice ECP5 devices have two global set/reset signals: PUR, which is driven at startup
        # by the configuration logic and unconditionally resets every storage element, and GSR,
        # which is driven by user logic and each storage element may be configured as affected or
        # unaffected by GSR. PUR is purely asynchronous, so even though it is a low-skew global
        # network, its deassertion may violate a setup/hold constraint with relation to a user
        # clock. To avoid this, a GSR/SGSR instance should be driven synchronized to user clock.
        if name == "sync" and self.default_clk is not None:
            m = Module()
            if self.default_clk == "OSCG":
                if not hasattr(self, "oscg_div"):
                    raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
                                     "and 128")
                if not isinstance(self.oscg_div, int) or self.oscg_div < 2 or self.oscg_div > 128:
                    raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
                                     "and 128, not {!r}"
                                     .format(self.oscg_div))
                clk_i = Signal()
                m.submodules += Instance("OSCG", p_DIV=self.oscg_div, o_OSC=clk_i)
            else:
                clk_i = self.request(self.default_clk).i
            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            gsr0 = Signal()
            gsr1 = Signal()
            # There is no end-of-startup signal on ECP5, but PUR is released after IOB enable, so
            # a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
            m.submodules += [
                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0,   o_Q=gsr1),
                # Although we already synchronize the reset input to user clock, SGSR has dedicated
                # clock routing to the center of the FPGA; use that just in case it turns out to be
                # more reliable. (None of this is documented.)
                Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
            ]
            # GSR implicitly connects to every appropriate storage element. As such, the sync
            # domain is reset-less; domains driven by other clocks would need to have dedicated
            # reset circuitry or otherwise meet setup/hold constraints on their own.
            m.domains += ClockDomain("sync", reset_less=True)
            m.d.comb += ClockSignal("sync").eq(clk_i)
            return m

    _single_ended_io_types = [
        "HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
        "SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II",
    ]
    _differential_io_types = [
        "BLVDS25", "BLVDS25E", "HSUL12D", "LVCMOS18D", "LVCMOS25D", "LVCMOS33D",
        "LVDS", "LVDS25E", "LVPECL33", "LVPECL33E", "LVTTL33D", "MLVDS", "MLVDS25E",
        "SLVS", "SSTL135D_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I",
        "SSTL18D_II", "SUBLVDS",
    ]

    def should_skip_port_component(self, port, attrs, component):
        # On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
        # the PIOA or PIOC location, which is always the non-inverting pin.
        if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
            return True
        return False

    def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
        def get_ireg(clk, d, q):
            for bit in range(len(q)):
                m.submodules += Instance("IFS1P3DX",
                    i_SCLK=clk,
                    i_SP=Const(1),
                    i_CD=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_oreg(clk, d, q):
            for bit in range(len(q)):
                m.submodules += Instance("OFS1P3DX",
                    i_SCLK=clk,
                    i_SP=Const(1),
                    i_CD=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_oereg(clk, oe, q):
            for bit in range(len(q)):
                m.submodules += Instance("OFS1P3DX",
                    i_SCLK=clk,
                    i_SP=Const(1),
                    i_CD=Const(0),
                    i_D=oe,
                    o_Q=q[bit]
                )

        def get_iddr(sclk, d, q0, q1):
            for bit in range(len(d)):
                m.submodules += Instance("IDDRX1F",
                    i_SCLK=sclk,
                    i_RST=Const(0),
                    i_D=d[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit]
                )

        def get_iddrx2(sclk, eclk, d, q0, q1, q2, q3):
            for bit in range(len(d)):
                m.submodules += Instance("IDDRX2F",
                    i_SCLK=sclk,
                    i_ECLK=eclk,
                    i_RST=Const(0),
                    i_D=d[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit]
                )

        def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6):
            for bit in range(len(d)):
                m.submodules += Instance("IDDR71B",
                    i_SCLK=sclk,
                    i_ECLK=eclk,
                    i_RST=Const(0),
                    i_D=d[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit],
                    o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[bit],
                )

        def get_oddr(sclk, d0, d1, q):
            for bit in range(len(q)):
                m.submodules += Instance("ODDRX1F",
                    i_SCLK=sclk,
                    i_RST=Const(0),
                    i_D0=d0[bit], i_D1=d1[bit],
                    o_Q=q[bit]
                )

        def get_oddrx2(sclk, eclk, d0, d1, d2, d3, q):
            for bit in range(len(q)):
                m.submodules += Instance("ODDRX2F",
                    i_SCLK=sclk,
                    i_ECLK=eclk,
                    i_RST=Const(0),
                    i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
                    o_Q=q[bit]
                )

        def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q):
            for bit in range(len(q)):
                m.submodules += Instance("ODDR71B",
                    i_SCLK=sclk,
                    i_ECLK=eclk,
                    i_RST=Const(0),
                    i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
                    i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit],
                    o_Q=q[bit]
                )

        def get_ineg(z, invert):
            if invert:
                a = Signal.like(z, name_suffix="_n")
                m.d.comb += z.eq(~a)
                return a
            else:
                return z

        def get_oneg(a, invert):
            if invert:
                z = Signal.like(a, name_suffix="_n")
                m.d.comb += z.eq(~a)
                return z
            else:
                return a

        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i  = get_ineg(pin.i,  i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
            elif pin.xdr == 4:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
                pin_i2 = get_ineg(pin.i2, i_invert)
                pin_i3 = get_ineg(pin.i3, i_invert)
            elif pin.xdr == 7:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
                pin_i2 = get_ineg(pin.i2, i_invert)
                pin_i3 = get_ineg(pin.i3, i_invert)
                pin_i4 = get_ineg(pin.i4, i_invert)
                pin_i5 = get_ineg(pin.i5, i_invert)
                pin_i6 = get_ineg(pin.i6, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o  = get_oneg(pin.o,  o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)
            elif pin.xdr == 4:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)
                pin_o2 = get_oneg(pin.o2, o_invert)
                pin_o3 = get_oneg(pin.o3, o_invert)
            elif pin.xdr == 7:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)
                pin_o2 = get_oneg(pin.o2, o_invert)
                pin_o3 = get_oneg(pin.o3, o_invert)
                pin_o4 = get_oneg(pin.o4, o_invert)
                pin_o5 = get_oneg(pin.o5, o_invert)
                pin_o6 = get_oneg(pin.o6, o_invert)

        i = o = t = None
        if "i" in pin.dir:
            i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
        if "o" in pin.dir:
            o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
        if pin.dir in ("oe", "io"):
            t = Signal(pin.width, name="{}_xdr_t".format(pin.name))

        if pin.xdr == 0:
            if "i" in pin.dir:
                i = pin_i
            if "o" in pin.dir:
                o = pin_o
            if pin.dir in ("oe", "io"):
                t = (~pin.oe).replicate(pin.width)
        elif pin.xdr == 1:
            if "i" in pin.dir:
                get_ireg(pin.i_clk, i, pin_i)
            if "o" in pin.dir:
                get_oreg(pin.o_clk, pin_o, o)
            if pin.dir in ("oe", "io"):
                get_oereg(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 2:
            if "i" in pin.dir:
                get_iddr(pin.i_clk, i, pin_i0, pin_i1)
            if "o" in pin.dir:
                get_oddr(pin.o_clk, pin_o0, pin_o1, o)
            if pin.dir in ("oe", "io"):
                get_oereg(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 4:
            if "i" in pin.dir:
                get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3)
            if "o" in pin.dir:
                get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o)
            if pin.dir in ("oe", "io"):
                get_oereg(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 7:
            if "i" in pin.dir:
                get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6)
            if "o" in pin.dir:
                get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o)
            if pin.dir in ("oe", "io"):
                get_oereg(pin.o_clk, ~pin.oe, t)
        else:
            assert False

        return (i, o, t)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
                i_I=port.io[bit],
                o_O=i[bit]
            )
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
                i_T=t[bit],
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
                i_T=t[bit],
                i_I=o[bit],
                o_O=i[bit],
                io_B=port.io[bit]
            )
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
                i_I=port.p[bit],
                o_O=i[bit]
            )
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
                i_I=o[bit],
                o_O=port.p[bit],
            )
        return m

    def get_diff_tristate(self, pin, port, attrs, invert):
        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
                i_T=t[bit],
                i_I=o[bit],
                o_O=port.p[bit],
            )
        return m

    def get_diff_input_output(self, pin, port, attrs, invert):
        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
                i_T=t[bit],
                i_I=o[bit],
                o_O=i[bit],
                io_B=port.p[bit],
            )
        return m

    # CDC primitives are not currently specialized for ECP5.
    # While Diamond supports false path constraints; nextpnr-ecp5 does not.


# amaranth/vendor/lattice_ice40.py

from abc import abstractmethod

from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *


__all__ = ["LatticeICE40Platform"]


class LatticeICE40Platform(TemplatedPlatform):
    """
    .. rubric:: IceStorm toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-ice40``
        * ``icepack``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_ICESTORM``, if present.

    Available overrides:
        * ``verbose``: enables logging of informational messages to standard error.
        * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
        * ``synth_opts``: adds options for ``synth_ice40`` Yosys command.
        * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
        * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
        * ``yosys_opts``: adds extra options for ``yosys``.
        * ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``.
        * ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
        * ``add_constraints``: inserts commands at the end in the PCF file.

    Build products:
        * ``{{name}}.rpt``: Yosys log.
        * ``{{name}}.json``: synthesized RTL.
        * ``{{name}}.tim``: nextpnr log.
        * ``{{name}}.asc``: ASCII bitstream.
        * ``{{name}}.bin``: binary bitstream.

    .. rubric:: iCECube2 toolchain

    This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.

    Required tools:
        * iCECube2 toolchain
        * ``tclsh``

    The environment is populated by setting the necessary environment variables based on
    ``AMARANTH_ENV_ICECUBE2``, which must point to the root of the iCECube2 installation, and
    is required.

    Available overrides:
        * ``verbose``: enables logging of informational messages to standard error.
        * ``lse_opts``: adds options for LSE.
        * ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
        * ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
        * ``add_constraints``: inserts commands in SDC file.
        * ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
          Tcl script.

    Build products:
        * ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
        * ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
        * ``{{name}}.edf``: EDIF netlist.
        * ``{{name}}.bin``: binary bitstream.
    """

    toolchain = None # selected when creating platform

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))

    # IceStorm templates

    _nextpnr_device_options = {
        "iCE40LP384": "--lp384",
        "iCE40LP1K":  "--lp1k",
        "iCE40LP4K":  "--lp8k",
        "iCE40LP8K":  "--lp8k",
        "iCE40HX1K":  "--hx1k",
        "iCE40HX4K":  "--hx8k",
        "iCE40HX8K":  "--hx8k",
        "iCE40UP5K":  "--up5k",
        "iCE40UP3K":  "--up5k",
        "iCE5LP4K":   "--u4k",
        "iCE5LP2K":   "--u4k",
        "iCE5LP1K":   "--u4k",
    }
    _nextpnr_package_options = {
        "iCE40LP4K":  ":4k",
        "iCE40HX4K":  ":4k",
        "iCE40UP3K":  "",
        "iCE5LP2K":   "",
        "iCE5LP1K":   "",
    }

    _icestorm_required_tools = [
        "yosys",
        "nextpnr-ice40",
        "icepack",
    ]
    _icestorm_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.il": r"""
            # {{autogenerated}}
            {{emit_rtlil()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.ys": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v") -%}
                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".il") -%}
                read_ilang {{file}}
            {% endfor %}
            read_ilang {{name}}.il
            delete w:$verilog_initial_trigger
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_ice40 {{get_override("synth_opts")|options}} -top {{name}}
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
            write_json {{name}}.json
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}}
            {% endfor%}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
    }
    _icestorm_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            {{quiet("-q")}}
            {{get_override("yosys_opts")|options}}
            -l {{name}}.rpt
            {{name}}.ys
        """,
        r"""
        {{invoke_tool("nextpnr-ice40")}}
            {{quiet("--quiet")}}
            {{get_override("nextpnr_opts")|options}}
            --log {{name}}.tim
            {{platform._nextpnr_device_options[platform.device]}}
            --package
                {{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]|
                                            default("")}}
            --json {{name}}.json
            --pcf {{name}}.pcf
            --asc {{name}}.asc
        """,
        r"""
        {{invoke_tool("icepack")}}
            {{verbose("-v")}}
            {{name}}.asc
            {{name}}.bin
        """
    ]

    # iCECube2 templates

    _icecube2_required_tools = [
        "synthesis",
        "synpwrap",
        "tclsh",
    ]
    _icecube2_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            {% for var in platform._all_toolchain_env_vars %}
            if [ -n "${{var}}" ]; then
                # LSE environment
                export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
                export PATH=${{var}}/LSE/bin/lin64:$PATH
                export FOUNDRY=${{var}}/LSE
                # Synplify environment
                export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
                export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
                export SYNPLIFY_PATH=${{var}}/synpbase
                # Common environment
                export SBT_DIR=${{var}}/sbt_backend
            else
                echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
            fi
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}_lse.prj": r"""
            # {{autogenerated}}
            -a SBT{{platform.family}}
            -d {{platform.device}}
            -t {{platform.package}}
            {{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
            {% for file in platform.iter_files(".v") -%}
                -ver {{file}}
            {% endfor %}
            -ver {{name}}.v
            -sdc {{name}}.sdc
            -top {{name}}
            -output_edif {{name}}.edf
            -logfile {{name}}_lse.log
        """,
        "{{name}}_syn.prj": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
                add_file -verilog {{file|tcl_escape}}
            {% endfor %}
            add_file -verilog {{name}}.v
            add_file -constraint {{name}}.sdc
            {{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
            impl -add {{name}}_design -type fpga
            set_option -technology SBT{{platform.family}}
            set_option -part {{platform.device}}
            set_option -package {{platform.package}}
            {{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
            project -result_format edif
            project -result_file {{name}}.edf
            impl -active {{name}}_design
            project -run compile
            project -run map
            project -run fpga_mapper
            file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
        """,
        "{{name}}.sdc": r"""
            # {{autogenerated}}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
                {% else -%}
                    create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
        "{{name}}.tcl": r"""
            # {{autogenerated}}
            set device {{platform.device}}-{{platform.package}}
            set top_module {{name}}
            set proj_dir .
            set output_dir .
            set edif_file {{name}}
            set tool_options ":edifparser -y {{name}}.pcf"
            set sbt_root $::env(SBT_DIR)
            append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
            source $sbt_tcl
            run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
            {{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
            file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
            exit
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
        """,
    }
    _lse_icecube2_command_templates = [
        r"""synthesis -f {{name}}_lse.prj""",
        r"""tclsh {{name}}.tcl""",
    ]
    _synplify_icecube2_command_templates = [
        r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
        r"""tclsh {{name}}.tcl""",
    ]

    # Common logic

    def __init__(self, *, toolchain="IceStorm"):
        super().__init__()

        assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
        self.toolchain = toolchain

    @property
    def family(self):
        if self.device.startswith("iCE40"):
            return "iCE40"
        if self.device.startswith("iCE5"):
            return "iCE5"
        assert False

    @property
    def _toolchain_env_var(self):
        if self.toolchain == "IceStorm":
            return f"AMARANTH_ENV_{self.toolchain}"
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return f"AMARANTH_ENV_ICECUBE2"
        assert False

    @property
    def required_tools(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_required_tools
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return self._icecube2_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_file_templates
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return self._icecube2_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_command_templates
        if self.toolchain == "LSE-iCECube2":
            return self._lse_icecube2_command_templates
        if self.toolchain == "Synplify-iCECube2":
            return self._synplify_icecube2_command_templates
        assert False

    @property
    def default_clk_constraint(self):
        # Internal high-speed oscillator: 48 MHz / (2 ^ div)
        if self.default_clk == "SB_HFOSC":
            return Clock(48e6 / 2 ** self.hfosc_div)
        # Internal low-speed oscillator: 10 KHz
        elif self.default_clk == "SB_LFOSC":
            return Clock(10e3)
        # Otherwise, use the defined Clock resource.
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        # For unknown reasons (no errata was ever published, and no documentation mentions this
        # issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
        # global reset. Note that this is a *time-based* delay, generated purely by the internal
        # oscillator, which may not be observed nor influenced directly. For details, see links:
        #  * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
        #  * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
        #
        # To handle this, it is necessary to have a global reset in any iCE40 design that may
        # potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
        # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
        # reset line, this line is ORed with the power on reset.
        #
        # If an internal oscillator is selected as the default clock source, the power-on-reset
        # delay is increased to 100 us, since the oscillators are only stable after that long.
        #
        # The power-on reset timer counts up because the vendor tools do not support initialization
        # of flip-flops.
        if name == "sync" and self.default_clk is not None:
            m = Module()

            # Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider.
            if self.default_clk == "SB_HFOSC":
                if not hasattr(self, "hfosc_div"):
                    raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
                                     "between 0 and 3")
                if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3:
                    raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
                                     "between 0 and 3, not {!r}"
                                     .format(self.hfosc_div))
                clk_i = Signal()
                m.submodules += Instance("SB_HFOSC",
                                         i_CLKHFEN=1,
                                         i_CLKHFPU=1,
                                         p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div),
                                         o_CLKHF=clk_i)
                delay = int(100e-6 * self.default_clk_frequency)
            # Internal low-speed clock: 10 KHz.
            elif self.default_clk == "SB_LFOSC":
                clk_i = Signal()
                m.submodules += Instance("SB_LFOSC",
                                         i_CLKLFEN=1,
                                         i_CLKLFPU=1,
                                         o_CLKLF=clk_i)
                delay = int(100e-6 * self.default_clk_frequency)
            # User-defined clock signal.
            else:
                clk_i = self.request(self.default_clk).i
                delay = int(15e-6 * self.default_clk_frequency)

            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            # Power-on-reset domain
            m.domains += ClockDomain("por", reset_less=True, local=True)
            timer = Signal(range(delay))
            ready = Signal()
            m.d.comb += ClockSignal("por").eq(clk_i)
            with m.If(timer == delay):
                m.d.por += ready.eq(1)
            with m.Else():
                m.d.por += timer.eq(timer + 1)

            # Primary domain
            m.domains += ClockDomain("sync")
            m.d.comb += ClockSignal("sync").eq(clk_i)
            if self.default_rst is not None:
                m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync")
            else:
                m.d.comb += ResetSignal("sync").eq(~ready)

            return m

    def should_skip_port_component(self, port, attrs, component):
        # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
        # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
        # between LP/HX and UP series:
        #  * for LP/HX, z=0 is DPxxB   (B is non-inverting, A is inverting)
        #  * for UP,    z=0 is IOB_xxA (A is non-inverting, B is inverting)
        if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n":
            return True
        return False

    def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False,
                       invert_lut=False):
        def get_dff(clk, d, q):
            m.submodules += Instance("$dff",
                p_CLK_POLARITY=1,
                p_WIDTH=len(d),
                i_CLK=clk,
                i_D=d,
                o_Q=q)

        def get_ineg(y, invert):
            if invert_lut:
                a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0))
                for bit in range(len(y)):
                    m.submodules += Instance("SB_LUT4",
                        p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
                        i_I0=a[bit],
                        i_I1=Const(0),
                        i_I2=Const(0),
                        i_I3=Const(0),
                        o_O=y[bit])
                return a
            elif invert:
                a = Signal.like(y, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return a
            else:
                return y

        def get_oneg(a, invert):
            if invert_lut:
                y = Signal.like(a, name_suffix="_x{}".format(1 if invert else 0))
                for bit in range(len(a)):
                    m.submodules += Instance("SB_LUT4",
                        p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
                        i_I0=a[bit],
                        i_I1=Const(0),
                        i_I2=Const(0),
                        i_I3=Const(0),
                        o_O=y[bit])
                return y
            elif invert:
                y = Signal.like(a, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return y
            else:
                return a

        if "GLOBAL" in attrs:
            is_global_input = bool(attrs["GLOBAL"])
            del attrs["GLOBAL"]
        else:
            is_global_input = False
        assert not (is_global_input and i_invert)

        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i  = get_ineg(pin.i,  i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o  = get_oneg(pin.o,  o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)

        if "i" in pin.dir and pin.xdr == 2:
            i0_ff = Signal.like(pin_i0, name_suffix="_ff")
            i1_ff = Signal.like(pin_i1, name_suffix="_ff")
            get_dff(pin.i_clk, i0_ff, pin_i0)
            get_dff(pin.i_clk, i1_ff, pin_i1)
        if "o" in pin.dir and pin.xdr == 2:
            o1_ff = Signal.like(pin_o1, name_suffix="_ff")
            get_dff(pin.o_clk, pin_o1, o1_ff)

        for bit in range(len(port)):
            io_args = [
                ("io", "PACKAGE_PIN", port[bit]),
                *(("p", key, value) for key, value in attrs.items()),
            ]

            if "i" not in pin.dir:
                # If no input pin is requested, it is important to use a non-registered input pin
                # type, because an output-only pin would not have an input clock, and if its input
                # is configured as registered, this would prevent a co-located input-capable pin
                # from using an input clock.
                i_type =     0b01 # PIN_INPUT
            elif pin.xdr == 0:
                i_type =     0b01 # PIN_INPUT
            elif pin.xdr > 0:
                i_type =     0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
            if "o" not in pin.dir:
                o_type = 0b0000   # PIN_NO_OUTPUT
            elif pin.xdr == 0 and pin.dir == "o":
                o_type = 0b0110   # PIN_OUTPUT
            elif pin.xdr == 0:
                o_type = 0b1010   # PIN_OUTPUT_TRISTATE
            elif pin.xdr == 1 and pin.dir == "o":
                o_type = 0b0101   # PIN_OUTPUT_REGISTERED
            elif pin.xdr == 1:
                o_type = 0b1101   # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
            elif pin.xdr == 2 and pin.dir == "o":
                o_type = 0b0100   # PIN_OUTPUT_DDR
            elif pin.xdr == 2:
                o_type = 0b1100   # PIN_OUTPUT_DDR_ENABLE_REGISTERED
            io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))

            if hasattr(pin, "i_clk"):
                io_args.append(("i", "INPUT_CLK",  pin.i_clk))
            if hasattr(pin, "o_clk"):
                io_args.append(("i", "OUTPUT_CLK", pin.o_clk))

            if "i" in pin.dir:
                if pin.xdr == 0 and is_global_input:
                    io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit]))
                elif pin.xdr < 2:
                    io_args.append(("o", "D_IN_0",  pin_i[bit]))
                elif pin.xdr == 2:
                    # Re-register both inputs before they enter fabric. This increases hold time
                    # to an entire cycle, and adds one cycle of latency.
                    io_args.append(("o", "D_IN_0",  i0_ff[bit]))
                    io_args.append(("o", "D_IN_1",  i1_ff[bit]))
            if "o" in pin.dir:
                if pin.xdr < 2:
                    io_args.append(("i", "D_OUT_0", pin_o[bit]))
                elif pin.xdr == 2:
                    # Re-register negedge output after it leaves fabric. This increases setup time
                    # to an entire cycle, and doesn't add latency.
                    io_args.append(("i", "D_OUT_0", pin_o0[bit]))
                    io_args.append(("i", "D_OUT_1", o1_ff[bit]))

            if pin.dir in ("oe", "io"):
                io_args.append(("i", "OUTPUT_ENABLE", pin.oe))

            if is_global_input:
                m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args)
            else:
                m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_IO", *io_args)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert)
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert)
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        # See comment in should_skip_port_component above.
        self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert)
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        # Note that the non-inverting output pin is not driven the same way as a regular
        # output pin. The inverter introduces a delay, so for a non-inverting output pin,
        # an identical delay is introduced by instantiating a LUT. This makes the waveform
        # perfectly symmetric in the xdr=0 case.
        self._get_io_buffer(m, pin, port.p, attrs, o_invert=    invert, invert_lut=True)
        self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True)
        return m

    # Tristate bidirectional buffers are not supported on iCE40 because it requires external
    # termination, which is different for differential pins configured as inputs and outputs.

    # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
    # the necessary attributes; nextpnr-ice40 does not.


# amaranth/vendor/lattice_machxo_2_3l.py

from abc import abstractmethod

from ..hdl import *
from ..build import *


__all__ = ["LatticeMachXO2Platform", "LatticeMachXO3LPlatform"]


# MachXO2 and MachXO3L primitives are the same. Handle both using
# one class and expose user-aliases for convenience.
class LatticeMachXO2Or3LPlatform(TemplatedPlatform):
    """
    Required tools:
        * ``pnmainc``
        * ``ddtcmd``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
    itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
    for instance) is known to work::

        @echo off
        set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%

    Available overrides:
        * ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
        * ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
        * ``add_preferences``: inserts commands at the end of the LPF file.
        * ``add_constraints``: inserts commands at the end of the XDC file.

    Build products:
        * ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
        * ``{{name}}.jed``: JEDEC fuse file.
        * ``{{name}}.bit``: binary bitstream.
        * ``{{name}}.svf``: JTAG programming vector for FLASH programming.
        * ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming.
        * ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming.
    """

    toolchain = "Diamond"

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))
    speed   = property(abstractmethod(lambda: None))
    grade   = "C" # [C]ommercial, [I]ndustrial

    required_tools = [
        "pnmainc",
        "ddtcmd"
    ]
    file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
            {% for var in platform._all_toolchain_env_vars %}
            if [ -n "${{var}}" ]; then
                bindir=$(dirname "${{var}}")
                . "${{var}}"
            fi
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.tcl": r"""
            prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
                -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
                -lpf {{name}}.lpf \
                -synthesis synplify
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
                prj_src add {{file|tcl_escape}}
            {% endfor %}
            prj_src add {{name}}.v
            prj_impl option top {{name}}
            prj_src add {{name}}.sdc
            {{get_override("script_project")|default("# (script_project placeholder)")}}
            prj_project save
            prj_run Synthesis -impl impl
            prj_run Translate -impl impl
            prj_run Map -impl impl
            prj_run PAR -impl impl
            prj_run Export -impl impl -task Bitgen
            prj_run Export -impl impl -task Jedecgen
            {{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
        """,
        "{{name}}.lpf": r"""
            # {{autogenerated}}
            BLOCK ASYNCPATHS;
            BLOCK RESETPATHS;
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
                {% if attrs -%}
                IOBUF PORT "{{port_name}}"
                    {%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
                {% endif %}
            {% endfor %}
            {{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
        """,
        "{{name}}.sdc": r"""
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
                {% else -%}
                    create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
    }
    command_templates = [
        # These don't have any usable command-line option overrides.
        r"""
        {{invoke_tool("pnmainc")}}
            {{name}}.tcl
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -bit
            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -jed
            -dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}}
            -if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -svfsingle -revd -op "FLASH Erase,Program,Verify"
            -if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf
        """,
        # TODO(amaranth-0.4): remove
        r"""
        {% if syntax == "bat" -%}
        copy {{name}}_flash.svf {{name}}.svf
        {% else -%}
        cp {{name}}_flash.svf {{name}}.svf
        {% endif %}
        """,
        r"""
        {{invoke_tool("ddtcmd")}}
            -oft -svfsingle -revd -op "SRAM Fast Program"
            -if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf
        """,
    ]
    # These numbers were extracted from
    # "MachXO2 sysCLOCK PLL Design and Usage Guide"
    _supported_osch_freqs = [
        2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89,
        3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59,
        4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00,
        7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64,
        11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63,
        17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33,
        53.20, 66.50, 88.67, 133.00
    ]

    @property
    def default_clk_constraint(self):
        # Internal high-speed oscillator on MachXO2/MachXO3L devices.
        # It can have a range of frequencies.
        if self.default_clk == "OSCH":
            assert self.osch_frequency in self._supported_osch_freqs
            return Clock(int(self.osch_frequency * 1e6))
        # Otherwise, use the defined Clock resource.
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        # Lattice MachXO2/MachXO3L devices have two global set/reset signals: PUR, which is driven at
        # startup by the configuration logic and unconditionally resets every storage element,
        # and GSR, which is driven by user logic and each storage element may be configured as
        # affected or unaffected by GSR. PUR is purely asynchronous, so even though it is
        # a low-skew global network, its deassertion may violate a setup/hold constraint with
        # relation to a user clock. To avoid this, a GSR/SGSR instance should be driven
        # synchronized to user clock.
        if name == "sync" and self.default_clk is not None:
            using_osch = False
            if self.default_clk == "OSCH":
                using_osch = True
                clk_i = Signal()
            else:
                clk_i = self.request(self.default_clk).i
            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            gsr0 = Signal()
            gsr1 = Signal()
            m = Module()
            # There is no end-of-startup signal on MachXO2/MachXO3L, but PUR is released after IOB
            # enable, so a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
            m.submodules += [
                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
                Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0,   o_Q=gsr1),
                # Although we already synchronize the reset input to user clock, SGSR has dedicated
                # clock routing to the center of the FPGA; use that just in case it turns out to be
                # more reliable. (None of this is documented.)
                Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
            ]
            if using_osch:
                osch_freq = self.osch_frequency
                if osch_freq not in self._supported_osch_freqs:
                    raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}"
                             .format(osch_freq, self._supported_osch_freqs))
                osch_freq_param = "{:.2f}".format(float(osch_freq))
                m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ]
            # GSR implicitly connects to every appropriate storage element. As such, the sync
            # domain is reset-less; domains driven by other clocks would need to have dedicated
            # reset circuitry or otherwise meet setup/hold constraints on their own.
            m.domains += ClockDomain("sync", reset_less=True)
            m.d.comb += ClockSignal("sync").eq(clk_i)
            return m

    _single_ended_io_types = [
        "PCI33", "LVTTL33", "LVCMOS33", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12",
        "LVCMOS25R33", "LVCMOS18R33", "LVCMOS18R25", "LVCMOS15R33", "LVCMOS15R25", "LVCMOS12R33",
        "LVCMOS12R25", "LVCMOS10R33", "LVCMOS10R25", "SSTL25_I", "SSTL25_II", "SSTL18_I",
        "SSTL18_II", "HSTL18_I", "HSTL18_II",
    ]
    _differential_io_types = [
        "LVDS25", "LVDS25E", "RSDS25", "RSDS25E", "BLVDS25", "BLVDS25E", "MLVDS25", "MLVDS25E",
        "LVPECL33", "LVPECL33E", "SSTL25D_I", "SSTL25D_II", "SSTL18D_I", "SSTL18D_II",
        "HSTL18D_I", "HSTL18D_II", "LVTTL33D", "LVCMOS33D", "LVCMOS25D", "LVCMOS18D", "LVCMOS15D",
        "LVCMOS12D", "MIPI",
    ]

    def should_skip_port_component(self, port, attrs, component):
        # On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
        # the PIOA or PIOC location, which is always the non-inverting pin.
        if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
            return True
        return False

    def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
        def get_ireg(clk, d, q):
            for bit in range(len(q)):
                m.submodules += Instance("IFS1P3DX",
                    i_SCLK=clk,
                    i_SP=Const(1),
                    i_CD=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_oreg(clk, d, q):
            for bit in range(len(q)):
                m.submodules += Instance("OFS1P3DX",
                    i_SCLK=clk,
                    i_SP=Const(1),
                    i_CD=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_iddr(sclk, d, q0, q1):
            for bit in range(len(d)):
                m.submodules += Instance("IDDRXE",
                    i_SCLK=sclk,
                    i_RST=Const(0),
                    i_D=d[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit]
                )

        def get_oddr(sclk, d0, d1, q):
            for bit in range(len(q)):
                m.submodules += Instance("ODDRXE",
                    i_SCLK=sclk,
                    i_RST=Const(0),
                    i_D0=d0[bit], i_D1=d1[bit],
                    o_Q=q[bit]
                )

        def get_ineg(z, invert):
            if invert:
                a = Signal.like(z, name_suffix="_n")
                m.d.comb += z.eq(~a)
                return a
            else:
                return z

        def get_oneg(a, invert):
            if invert:
                z = Signal.like(a, name_suffix="_n")
                m.d.comb += z.eq(~a)
                return z
            else:
                return a

        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i  = get_ineg(pin.i,  i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o  = get_oneg(pin.o,  o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)

        i = o = t = None
        if "i" in pin.dir:
            i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
        if "o" in pin.dir:
            o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
        if pin.dir in ("oe", "io"):
            t = Signal(1,         name="{}_xdr_t".format(pin.name))

        if pin.xdr == 0:
            if "i" in pin.dir:
                i = pin_i
            if "o" in pin.dir:
                o = pin_o
            if pin.dir in ("oe", "io"):
                t = ~pin.oe
        elif pin.xdr == 1:
            # Note that currently nextpnr will not pack an FF (*FS1P3DX) into the PIO.
            if "i" in pin.dir:
                get_ireg(pin.i_clk, i, pin_i)
            if "o" in pin.dir:
                get_oreg(pin.o_clk, pin_o, o)
            if pin.dir in ("oe", "io"):
                get_oreg(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 2:
            if "i" in pin.dir:
                get_iddr(pin.i_clk, i, pin_i0, pin_i1)
            if "o" in pin.dir:
                get_oddr(pin.o_clk, pin_o0, pin_o1, o)
            if pin.dir in ("oe", "io"):
                # It looks like Diamond will not pack an OREG as a tristate register in a DDR PIO.
                # It is not clear what is the recommended set of primitives for this task.
                # Similarly, nextpnr will not pack anything as a tristate register in a DDR PIO.
                get_oreg(pin.o_clk, ~pin.oe, t)
        else:
            assert False

        return (i, o, t)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(len(port)):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
                i_I=port.io[bit],
                o_O=i[bit]
            )
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(len(port)):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(len(port)):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
                i_T=t,
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(len(port)):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
                i_T=t,
                i_I=o[bit],
                o_O=i[bit],
                io_B=port.io[bit]
            )
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
                i_I=port.p[bit],
                o_O=i[bit]
            )
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
                i_I=o[bit],
                o_O=port.p[bit],
            )
        return m

    def get_diff_tristate(self, pin, port, attrs, invert):
        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
                i_T=t,
                i_I=o[bit],
                o_O=port.p[bit],
            )
        return m

    def get_diff_input_output(self, pin, port, attrs, invert):
        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
                i_T=t,
                i_I=o[bit],
                o_O=i[bit],
                io_B=port.p[bit],
            )
        return m

    # CDC primitives are not currently specialized for MachXO2/MachXO3L.


LatticeMachXO2Platform = LatticeMachXO2Or3LPlatform
LatticeMachXO3LPlatform = LatticeMachXO2Or3LPlatform


# amaranth/vendor/quicklogic.py

from abc import abstractmethod

from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *


__all__ = ["QuicklogicPlatform"]


class QuicklogicPlatform(TemplatedPlatform):
    """
    .. rubric:: Symbiflow toolchain

    Required tools:
        * ``symbiflow_synth``
        * ``symbiflow_pack``
        * ``symbiflow_place``
        * ``symbiflow_route``
        * ``symbiflow_write_fasm``
        * ``symbiflow_write_bitstream``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_QLSYMBIFLOW``, if present.

    Available overrides:
        * ``add_constraints``: inserts commands in XDC file.
    """

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))

    # Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet
    # we should distinguish the QuickLogic version from mainline one.
    # QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases
    toolchain = "QLSymbiflow"

    required_tools = [
        "symbiflow_synth",
        "symbiflow_pack",
        "symbiflow_place",
        "symbiflow_route",
        "symbiflow_write_fasm",
        "symbiflow_write_bitstream",
        "symbiflow_write_openocd",
    ]
    file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
        """,
        "{{name}}.xdc": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                {% for attr_name, attr_value in attrs.items() -%}
                    set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }]
                {% endfor %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
        "{{name}}.sdc": r"""
            # {{autogenerated}}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -period {{100000000/frequency}} {{port_signal.name|ascii_escape}}
                {% endif %}
            {% endfor %}
        """
    }
    command_templates = [
        r"""
        {{invoke_tool("symbiflow_synth")}}
            -t {{name}}
            -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v
            -d {{platform.device}}
            -p {{name}}.pcf
            -P {{platform.package}}
            -x {{name}}.xdc
        """,
        r"""
        {{invoke_tool("symbiflow_pack")}}
            -e {{name}}.eblif
            -d {{platform.device}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_place")}}
            -e {{name}}.eblif
            -d {{platform.device}}
            -p {{name}}.pcf
            -n {{name}}.net
            -P {{platform.package}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_route")}}
            -e {{name}}.eblif
            -d {{platform.device}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_write_fasm")}}
            -e {{name}}.eblif
            -d {{platform.device}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_write_bitstream")}}
            -f {{name}}.fasm
            -d {{platform.device}}
            -P {{platform.package}}
            -b {{name}}.bit
        """,
        # This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in
        # the QLSymbiflow v1.3.0 toolchain release.
        r"""
        python3 -m quicklogic_fasm.bitstream_to_openocd
            {{name}}.bit
            {{name}}.openocd
            --osc-freq {{platform.osc_freq}}
            --fpga-clk-divider {{platform.osc_div}}
        """,
    ]

    # Common logic

    @property
    def default_clk_constraint(self):
        if self.default_clk == "sys_clk0":
            return Clock(self.osc_freq / self.osc_div)
        return super().default_clk_constraint

    def add_clock_constraint(self, clock, frequency):
        super().add_clock_constraint(clock, frequency)
        clock.attrs["keep"] = "TRUE"

    def create_missing_domain(self, name):
        if name == "sync" and self.default_clk is not None:
            m = Module()
            if self.default_clk == "sys_clk0":
                if not hasattr(self, "osc_div"):
                    raise ValueError("OSC divider (osc_div) must be an integer between 2 "
                                     "and 512")
                if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512:
                    raise ValueError("OSC divider (osc_div) must be an integer between 2 "
                                     "and 512, not {!r}"
                                     .format(self.osc_div))
                if not hasattr(self, "osc_freq"):
                    raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
                                     "and 80000000")
                if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000:
                    raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
                                     "and 80000000, not {!r}"
                                     .format(self.osc_freq))
                clk_i = Signal()
                sys_clk0 = Signal()
                m.submodules += Instance("qlal4s3b_cell_macro",
                                         o_Sys_Clk0=sys_clk0)
                m.submodules += Instance("gclkbuff",
                                         o_A=sys_clk0,
                                         o_Z=clk_i)
            else:
                clk_i = self.request(self.default_clk).i

            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            m.domains += ClockDomain("sync")
            m.d.comb += ClockSignal("sync").eq(clk_i)
            m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
            return m


# amaranth/vendor/xilinx.py

import re
from abc import abstractmethod

from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *


__all__ = ["XilinxPlatform"]


class XilinxPlatform(TemplatedPlatform):
    """
    .. rubric:: Vivado toolchain

    Required tools:
        * ``vivado``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_VIVADO``, if present.

    Available overrides:
        * ``script_after_read``: inserts commands after ``read_xdc`` in Tcl script.
        * ``synth_design_opts``: sets options for ``synth_design``.
        * ``script_after_synth``: inserts commands after ``synth_design`` in Tcl script.
        * ``script_after_place``: inserts commands after ``place_design`` in Tcl script.
        * ``script_after_route``: inserts commands after ``route_design`` in Tcl script.
        * ``script_before_bitstream``: inserts commands before ``write_bitstream`` in Tcl script.
        * ``script_after_bitstream``: inserts commands after ``write_bitstream`` in Tcl script.
        * ``add_constraints``: inserts commands in XDC file.
        * ``vivado_opts``: adds extra options for ``vivado``.

    Build products:
        * ``{{name}}.log``: Vivado log.
        * ``{{name}}_timing_synth.rpt``: Vivado report.
        * ``{{name}}_utilization_hierarchical_synth.rpt``: Vivado report.
        * ``{{name}}_utilization_synth.rpt``: Vivado report.
        * ``{{name}}_utilization_hierarchical_place.rpt``: Vivado report.
        * ``{{name}}_utilization_place.rpt``: Vivado report.
        * ``{{name}}_io.rpt``: Vivado report.
        * ``{{name}}_control_sets.rpt``: Vivado report.
        * ``{{name}}_clock_utilization.rpt``:  Vivado report.
        * ``{{name}}_route_status.rpt``: Vivado report.
        * ``{{name}}_drc.rpt``: Vivado report.
        * ``{{name}}_methodology.rpt``: Vivado report.
        * ``{{name}}_timing.rpt``: Vivado report.
        * ``{{name}}_power.rpt``: Vivado report.
        * ``{{name}}_route.dcp``: Vivado design checkpoint.
        * ``{{name}}.bit``: binary bitstream with metadata.
        * ``{{name}}.bin``: binary bitstream.

    .. rubric:: ISE toolchain

    Required tools:
        * ``xst``
        * ``ngdbuild``
        * ``map``
        * ``par``
        * ``bitgen``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_ISE``, if present.

    Available overrides:
        * ``script_after_run``: inserts commands after ``run`` in XST script.
        * ``add_constraints``: inserts commands in UCF file.
        * ``xst_opts``: adds extra options for ``xst``.
        * ``ngdbuild_opts``: adds extra options for ``ngdbuild``.
        * ``map_opts``: adds extra options for ``map``.
        * ``par_opts``: adds extra options for ``par``.
        * ``bitgen_opts``: adds extra and overrides default options for ``bitgen``;
          default options: ``-g Compress``.

    Build products:
        * ``{{name}}.srp``: synthesis report.
        * ``{{name}}.ngc``: synthesized RTL.
        * ``{{name}}.bld``: NGDBuild log.
        * ``{{name}}.ngd``: design database.
        * ``{{name}}_map.map``: MAP log.
        * ``{{name}}_map.mrp``: mapping report.
        * ``{{name}}_map.ncd``: mapped netlist.
        * ``{{name}}.pcf``: physical constraints.
        * ``{{name}}_par.par``: PAR log.
        * ``{{name}}_par_pad.txt``: I/O usage report.
        * ``{{name}}_par.ncd``: place and routed netlist.
        * ``{{name}}.drc``: DRC report.
        * ``{{name}}.bgn``: BitGen log.
        * ``{{name}}.bit``: binary bitstream with metadata.
        * ``{{name}}.bin``: raw binary bitstream.

    .. rubric:: Symbiflow toolchain

    Required tools:
        * ``symbiflow_synth``
        * ``symbiflow_pack``
        * ``symbiflow_place``
        * ``symbiflow_route``
        * ``symbiflow_write_fasm``
        * ``symbiflow_write_bitstream``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_SYMBIFLOW``, if present.

    Available overrides:
        * ``add_constraints``: inserts commands in XDC file.

    .. rubric:: Xray toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-xilinx``
        * ``fasm2frames``
        * ``xc7frames2bit``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_XRAY``, if present.
    """

    toolchain = None # selected when creating platform

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))
    speed   = property(abstractmethod(lambda: None))

    @property
    def _part(self):
        if self.family in {"ultrascale", "ultrascaleplus"}:
            return "{}-{}-{}".format(self.device, self.package, self.speed)
        else:
            return "{}{}-{}".format(self.device, self.package, self.speed)

    @property
    def vendor_toolchain(self):
        return self.toolchain in ["Vivado", "ISE"]

    # Vivado templates

    _vivado_required_tools = ["vivado"]
    _vivado_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
            {% for var in platform._all_toolchain_env_vars %}
            [ -n "${{var}}" ] && . "${{var}}"
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.tcl": r"""
            # {{autogenerated}}
            create_project -force -name {{name}} -part {{platform._part}}
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
                add_files {{file|tcl_escape}}
            {% endfor %}
            add_files {{name}}.v
            read_xdc {{name}}.xdc
            {% for file in platform.iter_files(".xdc") -%}
                read_xdc {{file|tcl_escape}}
            {% endfor %}
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_design -top {{name}} {{get_override("synth_design_opts")}}
            foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] {
                set_false_path -to $cell
            }
            foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] {
                set clock [get_clocks -of_objects \
                    [all_fanin -flat -startpoints_only [get_pin $cell/D]]]
                if {[llength $clock] != 0} {
                    set_max_delay -datapath_only -from $clock \
                        -to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell]
                }
            }
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
            report_timing_summary -file {{name}}_timing_synth.rpt
            report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt
            report_utilization -file {{name}}_utilization_synth.rpt
            opt_design
            place_design
            {{get_override("script_after_place")|default("# (script_after_place placeholder)")}}
            report_utilization -hierarchical -file {{name}}_utilization_hierarchical_place.rpt
            report_utilization -file {{name}}_utilization_place.rpt
            report_io -file {{name}}_io.rpt
            report_control_sets -verbose -file {{name}}_control_sets.rpt
            report_clock_utilization -file {{name}}_clock_utilization.rpt
            route_design
            {{get_override("script_after_route")|default("# (script_after_route placeholder)")}}
            phys_opt_design
            report_timing_summary -no_header -no_detailed_paths
            write_checkpoint -force {{name}}_route.dcp
            report_route_status -file {{name}}_route_status.rpt
            report_drc -file {{name}}_drc.rpt
            report_methodology -file {{name}}_methodology.rpt
            report_timing_summary -datasheet -max_paths 10 -file {{name}}_timing.rpt
            report_power -file {{name}}_power.rpt
            {{get_override("script_before_bitstream")|default("# (script_before_bitstream placeholder)")}}
            write_bitstream -force -bin_file {{name}}.bit
            {{get_override("script_after_bitstream")|default("# (script_after_bitstream placeholder)")}}
            quit
        """,
        "{{name}}.xdc": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}]
                {% for attr_name, attr_value in attrs.items() -%}
                    set_property {{attr_name}} {{attr_value|tcl_escape}} [get_ports {{port_name|tcl_escape}}]
                {% endfor %}
            {% endfor %}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
                {% else -%}
                    create_clock -name {{net_signal.name|ascii_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """
    }
    _vivado_command_templates = [
        r"""
        {{invoke_tool("vivado")}}
            {{verbose("-verbose")}}
            {{get_override("vivado_opts")|options}}
            -mode batch
            -log {{name}}.log
            -source {{name}}.tcl
        """
    ]

    # ISE toolchain

    _ise_required_tools = [
        "xst",
        "ngdbuild",
        "map",
        "par",
        "bitgen",
    ]
    _ise_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
            {% for var in platform._all_toolchain_env_vars %}
            [ -n "${{var}}" ] && . "${{var}}"
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.prj": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".vhd", ".vhdl") -%}
                vhdl work {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".v") -%}
                verilog work {{file}}
            {% endfor %}
            verilog work {{name}}.v
        """,
        "{{name}}.xst": r"""
            # {{autogenerated}}
            run
            -ifn {{name}}.prj
            -ofn {{name}}.ngc
            -top {{name}}
            -use_new_parser yes
            -p {{platform.device}}{{platform.package}}-{{platform.speed}}
            {{get_override("script_after_run")|default("# (script_after_run placeholder)")}}
        """,
        "{{name}}.ucf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                {% set port_name = port_name|replace("[", "<")|replace("]", ">") -%}
                NET "{{port_name}}" LOC={{pin_name}};
                {% for attr_name, attr_value in attrs.items() -%}
                    NET "{{port_name}}" {{attr_name}}={{attr_value}};
                {% endfor %}
            {% endfor %}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                NET "{{net_signal|hierarchy("/")}}" TNM_NET="PRD{{net_signal|hierarchy("/")}}";
                TIMESPEC "TS{{net_signal|hierarchy("__")}}"=PERIOD "PRD{{net_signal|hierarchy("/")}}" {{1000000000/frequency}} ns HIGH 50%;
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """
    }
    _ise_command_templates = [
        r"""
        {{invoke_tool("xst")}}
            {{get_override("xst_opts")|options}}
            -ifn {{name}}.xst
        """,
        r"""
        {{invoke_tool("ngdbuild")}}
            {{quiet("-quiet")}}
            {{verbose("-verbose")}}
            {{get_override("ngdbuild_opts")|options}}
            -uc {{name}}.ucf
            {{name}}.ngc
        """,
        r"""
        {{invoke_tool("map")}}
            {{verbose("-detail")}}
            {{get_override("map_opts")|default([])|options}}
            -w
            -o {{name}}_map.ncd
            {{name}}.ngd
            {{name}}.pcf
        """,
        r"""
        {{invoke_tool("par")}}
            {{get_override("par_opts")|default([])|options}}
            -w
            {{name}}_map.ncd
            {{name}}_par.ncd
            {{name}}.pcf
        """,
        r"""
        {{invoke_tool("bitgen")}}
            {{get_override("bitgen_opts")|default(["-g Compress"])|options}}
            -w
            -g Binary:Yes
            {{name}}_par.ncd
            {{name}}.bit
        """
    ]

    # Symbiflow templates

    # symbiflow does not distinguish between speed grades
    # TODO: join with _xray_part
    @property
    def _symbiflow_part(self):
        # drop the trailing speed grade letter(s), if any
        part = re.sub(r"[^\d]+$", "", self._part)
        # drop temp/speed grade letters after family name, if any
        part = re.sub(r"(.{4}\d+t)[il]", r"\1", part)
        return part

    # bitstream device name according to prjxray-db path
    # TODO: join with _xray_family
    @property
    def _symbiflow_bitstream_device(self):
        if self._part.startswith("xc7a"):
            return "artix7"
        elif self._part.startswith("xc7k"):
            return "kintex7"
        elif self._part.startswith("xc7z"):
            return "zynq7"
        elif self._part.startswith("xc7s"):
            return "spartan7"
        else:
            print("Unknown bitstream device for part {}".format(self._part))
            raise ValueError

    # device naming according to part_db.yml of f4pga project
    @property
    def _symbiflow_device(self):
        if self._part.startswith("xc7a35") or self._part.startswith("xc7a50"):
            return "xc7a50t_test"
        elif self._part.startswith("xc7a100"):
            return "xc7a100t_test"
        elif self._part.startswith("xc7a200"):
            return "xc7a200t_test"
        else:
            print("Unknown symbiflow device for part {}".format(self._part))
            raise ValueError


    _symbiflow_required_tools = [
        "symbiflow_synth",
        "symbiflow_pack",
        "symbiflow_place",
        "symbiflow_route",
        "symbiflow_write_fasm",
        "symbiflow_write_bitstream"
    ]
    _symbiflow_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
        """,
        "{{name}}.xdc": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                {% for attr_name, attr_value in attrs.items() -%}
                    set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }]
                {% endfor %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
        "{{name}}.sdc": r"""
            # {{autogenerated}}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is none -%}
                    create_clock -period {{1000000000/frequency}} {{net_signal.name|ascii_escape}}
                {% endif %}
            {% endfor %}
        """
    }
    _symbiflow_command_templates = [
        r"""
        {{invoke_tool("symbiflow_synth")}}
            -t {{name}}
            -v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v
            -p {{platform._symbiflow_part}}
            -d {{platform._symbiflow_bitstream_device}}
            -x {{name}}.xdc
        """,
        r"""
        {{invoke_tool("symbiflow_pack")}}
            -e {{name}}.eblif
            -d {{platform._symbiflow_device}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_place")}}
            -e {{name}}.eblif
            -p {{name}}.pcf
            -n {{name}}.net
            -P {{platform._symbiflow_part}}
            -d {{platform._symbiflow_device}}
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_route")}}
            -e {{name}}.eblif
            -P {{platform._symbiflow_part}}
            -d {{platform._symbiflow_device}}
            -s {{name}}.sdc
            -s {{name}}.sdc
        """,
        r"""
        {{invoke_tool("symbiflow_write_fasm")}}
            -e {{name}}.eblif
            -P {{platform._symbiflow_part}}
            -d {{platform._symbiflow_device}}
        """,
        r"""
        {{invoke_tool("symbiflow_write_bitstream")}}
            -f {{name}}.fasm
            -p {{platform._symbiflow_part}}
            -d {{platform._symbiflow_bitstream_device}}
            -b {{name}}.bit
        """
    ]

    # Yosys NextPNR prjxray templates

    @property
    def _xray_part(self):
        return {
            "xc7a35ticsg324-1L":  "xc7a35tcsg324-1",  # Arty-A7 35t
            "xc7a100ticsg324-1L": "xc7a100tcsg324-1", # Arty-A7 100t
        }.get(self._part, self._part)

    @property
    def _xray_device(self):
        return {
            "xc7a35ti":  "xc7a35t",
            "xc7a100ti": "xc7a100t",
        }.get(self.device, self.device)

    @property
    def _xray_family(self):
        return {
            "xc7a": "artix7",
            "xc7z": "zynq7",
        }[self.device[:4]]

    _xray_required_tools = [
        "yosys",
        "nextpnr-xilinx",
        "fasm2frames",
        "xc7frames2bit"
    ]
    _xray_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            {% for var in platform._all_toolchain_env_vars %}
            [ -n "${{var}}" ] && . "${{var}}"
            {% endfor %}
            : ${DB_DIR:=/usr/share/nextpnr/prjxray-db}
            : ${CHIPDB_DIR:=/usr/share/nextpnr/xilinx-chipdb}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.xdc": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                {% for attr_name, attr_value in attrs.items() -%}
                    set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}}]
                    set_property LOC {{pin_name}} [get_ports {{port_name|tcl_escape}}]
                {% endfor %}
            {% endfor %}
        """,
    }
    _xray_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            -p "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {{name}}; write_json {{name}}.json"
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %}
            {{name}}.v
        """,
        r"""
        {{invoke_tool("nextpnr-xilinx")}}
            --chipdb $CHIPDB_DIR/{{platform._xray_device}}.bin
            --xdc {{name}}.xdc
            --json {{name}}.json
            --write {{name}}_routed.json
            --fasm {{name}}.fasm
        """,
        r"""
        {{invoke_tool("fasm2frames")}}
            --part {{platform._xray_part}}
            --db-root $DB_DIR/{{platform._xray_family}} {{name}}.fasm > {{name}}.frames
        """,
        r"""
        {{invoke_tool("xc7frames2bit")}}
            --part_file $DB_DIR/{{platform._xray_family}}/{{platform._xray_part}}/part.yaml
            --part_name {{platform._xray_part}}
            --frm_file {{name}}.frames
            --output_file {{name}}.bit
        """,
    ]

    # Common logic

    def __init__(self, *, toolchain=None):
        super().__init__()

        # Determine device family.
        device = self.device.lower()
        # Remove the prefix.
        if device.startswith("xc"):
            device = device[2:]
        elif device.startswith("xa"):
            device = device[2:]
        elif device.startswith("xqr"):
            device = device[3:]
        elif device.startswith("xq"):
            device = device[2:]
        else:
            raise ValueError("Device '{}' is not recognized".format(self.device))
        # Do actual name matching.
        if device.startswith("2vp"):
            self.family = "virtex2p"
        elif device.startswith("2v"):
            self.family = "virtex2"
        elif device.startswith("3sd"):
            self.family = "spartan3adsp"
        elif device.startswith("3s"):
            if device.endswith("a"):
                self.family = "spartan3a"
            elif device.endswith("e"):
                self.family = "spartan3e"
            else:
                self.family = "spartan3"
        elif device.startswith("4v"):
            self.family = "virtex4"
        elif device.startswith("5v"):
            self.family = "virtex5"
        elif device.startswith("6v"):
            self.family = "virtex6"
        elif device.startswith("6s"):
            self.family = "spartan6"
        elif device.startswith("7"):
            self.family = "series7"
        elif device.startswith(("vu", "ku")):
            if device.endswith("p"):
                self.family = "ultrascaleplus"
            else:
                self.family = "ultrascale"
        elif device.startswith(("zu", "u", "k26", "au")):
            self.family = "ultrascaleplus"
        elif device.startswith(("v", "2s")):
            # Match last to avoid conflict with ultrascale.
            # Yes, Spartan 2 is the same thing as Virtex.
            if device.endswith("e"):
                self.family = "virtexe"
            else:
                self.family = "virtex"


        ISE_FAMILIES = {
                "virtex", "virtexe",
                "virtex2", "virtex2p",
                "spartan3", "spartan3e", "spartan3a", "spartan3adsp",
                "virtex4",
                "virtex5",
                "virtex6",
                "spartan6",
        }
        if toolchain is None:
            if self.family in ISE_FAMILIES:
                toolchain = "ISE"
            else:
                toolchain = "Vivado"

        assert toolchain in ("Vivado", "ISE", "Symbiflow", "Xray")
        if toolchain == "Vivado":
            if self.family in ISE_FAMILIES:
                raise ValueError("Family '{}' is not supported by the Vivado toolchain, please use ISE instead".format(self.family))
        elif toolchain == "ISE":
            if self.family not in ISE_FAMILIES and self.family != "series7":
                raise ValueError("Family '{}' is not supported by the ISE toolchain, please use Vivado instead".format(self.family))
        elif toolchain == "Symbiflow":
            if self.family != "series7":
                raise ValueError("Family '{}' is not supported by the Symbiflow toolchain".format(self.family))
        elif toolchain == "Xray":
            if self.family != "series7":
                raise ValueError("Family '{}' is not supported by the yosys nextpnr toolchain".format(self.family))

        self.toolchain = toolchain

    @property
    def required_tools(self):
        if self.toolchain == "Vivado":
            return self._vivado_required_tools
        if self.toolchain == "ISE":
            return self._ise_required_tools
        if self.toolchain == "Symbiflow":
            return self._symbiflow_required_tools
        if self.toolchain == "Xray":
            return self._xray_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "Vivado":
            return self._vivado_file_templates
        if self.toolchain == "ISE":
            return self._ise_file_templates
        if self.toolchain == "Symbiflow":
            return self._symbiflow_file_templates
        if self.toolchain == "Xray":
            return self._xray_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "Vivado":
            return self._vivado_command_templates
        if self.toolchain == "ISE":
            return self._ise_command_templates
        if self.toolchain == "Symbiflow":
            return self._symbiflow_command_templates
        if self.toolchain == "Xray":
            return self._xray_command_templates
        assert False

    def create_missing_domain(self, name):
        # Xilinx devices have a global write enable (GWE) signal that asserted during configuration
        # and deasserted once it ends. Because it is an asynchronous signal (GWE is driven by logic
        # synchronous to configuration clock, which is not used by most designs), even though it is
        # a low-skew global network, its deassertion may violate a setup/hold constraint with
        # relation to a user clock. The recommended solution is to use a BUFGCE driven by the EOS
        # signal (if available). For details, see:
        #   * https://www.xilinx.com/support/answers/44174.html
        #   * https://www.xilinx.com/support/documentation/white_papers/wp272.pdf

        STARTUP_PRIMITIVE = {
                "spartan6": "STARTUP_SPARTAN6",
                "virtex4": "STARTUP_VIRTEX4",
                "virtex5": "STARTUP_VIRTEX5",
                "virtex6": "STARTUP_VIRTEX6",
                "series7": "STARTUPE2",
                "ultrascale": "STARTUPE3",
                "ultrascaleplus": "STARTUPE3",
        }

        if self.family not in STARTUP_PRIMITIVE or not self.vendor_toolchain:
            # Spartan 3 and before lacks a STARTUP primitive with EOS output; use a simple ResetSynchronizer
            # in that case, as is the default.
            # Symbiflow does not support the STARTUPE2 primitive.
            return super().create_missing_domain(name)

        if name == "sync" and self.default_clk is not None:
            clk_i = self.request(self.default_clk).i
            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i

            m = Module()
            ready = Signal()
            m.submodules += Instance(STARTUP_PRIMITIVE[self.family], o_EOS=ready)
            m.domains += ClockDomain("sync", reset_less=self.default_rst is None)
            if self.toolchain != "Vivado":
                m.submodules += Instance("BUFGCE", i_CE=ready, i_I=clk_i, o_O=ClockSignal("sync"))
            elif self.family == "series7":
                # Actually use BUFGCTRL configured as BUFGCE, since using BUFGCE causes
                # sim/synth mismatches with Vivado 2019.2, and the suggested workaround
                # (SIM_DEVICE parameter) breaks Vivado 2017.4.
                m.submodules += Instance("BUFGCTRL",
                    p_SIM_DEVICE="7SERIES",
                    i_I0=clk_i,   i_S0=C(1, 1), i_CE0=ready,   i_IGNORE0=C(0, 1),
                    i_I1=C(1, 1), i_S1=C(0, 1), i_CE1=C(0, 1), i_IGNORE1=C(1, 1),
                    o_O=ClockSignal("sync")
                )
            else:
                m.submodules += Instance("BUFGCE",
                    p_SIM_DEVICE="ULTRASCALE",
                    i_CE=ready,
                    i_I=clk_i,
                    o_O=ClockSignal("sync")
                )
            if self.default_rst is not None:
                m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
            return m

    def add_clock_constraint(self, clock, frequency):
        super().add_clock_constraint(clock, frequency)
        clock.attrs["keep"] = "TRUE"

    def _get_xdr_buffer(self, m, pin, iostd, *, i_invert=False, o_invert=False):
        XFDDR_FAMILIES = {
            "virtex2",
            "virtex2p",
            "spartan3",
        }
        XDDR2_FAMILIES = {
            "spartan3e",
            "spartan3a",
            "spartan3adsp",
            "spartan6",
        }
        XDDR_FAMILIES = {
            "virtex4",
            "virtex5",
            "virtex6",
            "series7",
        }
        XDDRE1_FAMILIES = {
            "ultrascale",
            "ultrascaleplus",
        }

        def get_iob_dff(clk, d, q):
            # SDR I/O is performed by packing a flip-flop into the pad IOB.
            for bit in range(len(q)):
                m.submodules += Instance("FDCE",
                    a_IOB="TRUE",
                    i_C=clk,
                    i_CE=Const(1),
                    i_CLR=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_dff(clk, d, q):
            for bit in range(len(q)):
                m.submodules += Instance("FDCE",
                    i_C=clk,
                    i_CE=Const(1),
                    i_CLR=Const(0),
                    i_D=d[bit],
                    o_Q=q[bit]
                )

        def get_ifddr(clk, io, q0, q1):
            assert self.family in XFDDR_FAMILIES
            for bit in range(len(q0)):
                m.submodules += Instance("IFDDRCPE",
                    i_C0=clk, i_C1=~clk,
                    i_CE=Const(1),
                    i_CLR=Const(0), i_PRE=Const(0),
                    i_D=io[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit]
                )

        def get_iddr2(clk, d, q0, q1, alignment):
            assert self.family in XDDR2_FAMILIES
            for bit in range(len(q0)):
                m.submodules += Instance("IDDR2",
                    p_DDR_ALIGNMENT=alignment,
                    p_SRTYPE="ASYNC",
                    p_INIT_Q0=C(0, 1), p_INIT_Q1=C(0, 1),
                    i_C0=clk, i_C1=~clk,
                    i_CE=Const(1),
                    i_S=Const(0), i_R=Const(0),
                    i_D=d[bit],
                    o_Q0=q0[bit], o_Q1=q1[bit]
                )

        def get_iddr(clk, d, q1, q2):
            assert self.family in XDDR_FAMILIES or self.family in XDDRE1_FAMILIES
            for bit in range(len(q1)):
                if self.family in XDDR_FAMILIES:
                    m.submodules += Instance("IDDR",
                        p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED",
                        p_SRTYPE="ASYNC",
                        p_INIT_Q1=C(0, 1), p_INIT_Q2=C(0, 1),
                        i_C=clk,
                        i_CE=Const(1),
                        i_S=Const(0), i_R=Const(0),
                        i_D=d[bit],
                        o_Q1=q1[bit], o_Q2=q2[bit]
                    )
                else:
                    m.submodules += Instance("IDDRE1",
                        p_DDR_CLK_EDGE="SAME_EDGE_PIPELINED",
                        p_IS_C_INVERTED=C(0, 1), p_IS_CB_INVERTED=C(1, 1),
                        i_C=clk, i_CB=clk,
                        i_R=Const(0),
                        i_D=d[bit],
                        o_Q1=q1[bit], o_Q2=q2[bit]
                    )

        def get_fddr(clk, d0, d1, q):
            for bit in range(len(q)):
                if self.family in XFDDR_FAMILIES:
                    m.submodules += Instance("FDDRCPE",
                        i_C0=clk, i_C1=~clk,
                        i_CE=Const(1),
                        i_PRE=Const(0), i_CLR=Const(0),
                        i_D0=d0[bit], i_D1=d1[bit],
                        o_Q=q[bit]
                    )
                else:
                    m.submodules += Instance("ODDR2",
                        p_DDR_ALIGNMENT="NONE",
                        p_SRTYPE="ASYNC",
                        p_INIT=C(0, 1),
                        i_C0=clk, i_C1=~clk,
                        i_CE=Const(1),
                        i_S=Const(0), i_R=Const(0),
                        i_D0=d0[bit], i_D1=d1[bit],
                        o_Q=q[bit]
                    )

        def get_oddr(clk, d1, d2, q):
            for bit in range(len(q)):
                if self.family in XDDR2_FAMILIES:
                    m.submodules += Instance("ODDR2",
                        p_DDR_ALIGNMENT="C0",
                        p_SRTYPE="ASYNC",
                        p_INIT=C(0, 1),
                        i_C0=clk, i_C1=~clk,
                        i_CE=Const(1),
                        i_S=Const(0), i_R=Const(0),
                        i_D0=d1[bit], i_D1=d2[bit],
                        o_Q=q[bit]
                    )
                elif self.family in XDDR_FAMILIES:
                    m.submodules += Instance("ODDR",
                        p_DDR_CLK_EDGE="SAME_EDGE",
                        p_SRTYPE="ASYNC",
                        p_INIT=C(0, 1),
                        i_C=clk,
                        i_CE=Const(1),
                        i_S=Const(0), i_R=Const(0),
                        i_D1=d1[bit], i_D2=d2[bit],
                        o_Q=q[bit]
                    )
                elif self.family in XDDRE1_FAMILIES:
                    m.submodules += Instance("ODDRE1",
                        p_SRVAL=C(0, 1),
                        i_C=clk,
                        i_SR=Const(0),
                        i_D1=d1[bit], i_D2=d2[bit],
                        o_Q=q[bit]
                    )

        def get_ineg(y, invert):
            if invert:
                a = Signal.like(y, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return a
            else:
                return y

        def get_oneg(a, invert):
            if invert:
                y = Signal.like(a, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return y
            else:
                return a

        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i  = get_ineg(pin.i,  i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o  = get_oneg(pin.o,  o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)

        i = o = t = None
        if "i" in pin.dir:
            i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
        if "o" in pin.dir:
            o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
        if pin.dir in ("oe", "io"):
            t = Signal(1,         name="{}_xdr_t".format(pin.name))

        if pin.xdr == 0:
            if "i" in pin.dir:
                i = pin_i
            if "o" in pin.dir:
                o = pin_o
            if pin.dir in ("oe", "io"):
                t = ~pin.oe
        elif pin.xdr == 1:
            if "i" in pin.dir:
                get_iob_dff(pin.i_clk, i, pin_i)
            if "o" in pin.dir:
                get_iob_dff(pin.o_clk, pin_o, o)
            if pin.dir in ("oe", "io"):
                get_iob_dff(pin.o_clk, ~pin.oe, t)
        elif pin.xdr == 2:
            # On Spartan 3E/3A, the situation with DDR registers is messy: while the hardware
            # supports same-edge alignment, it does so by borrowing the resources of the other
            # pin in the differential pair (if any).  Since we cannot be sure if the other pin
            # is actually unused (or if the pin is even part of a differential pair in the first
            # place), we only use the hardware alignment feature in two cases:
            #
            # - differential inputs (since the other pin's input registers will be unused)
            # - true differential outputs (since they use only one pin's output registers,
            #   as opposed to pseudo-differential outputs that use both)
            TRUE_DIFF_S3EA = {
                "LVDS_33", "LVDS_25",
                "MINI_LVDS_33", "MINI_LVDS_25",
                "RSDS_33", "RSDS_25",
                "PPDS_33", "PPDS_25",
                "TMDS_33",
            }
            DIFF_S3EA = TRUE_DIFF_S3EA | {
                "DIFF_HSTL_I",
                "DIFF_HSTL_III",
                "DIFF_HSTL_I_18",
                "DIFF_HSTL_II_18",
                "DIFF_HSTL_III_18",
                "DIFF_SSTL3_I",
                "DIFF_SSTL3_II",
                "DIFF_SSTL2_I",
                "DIFF_SSTL2_II",
                "DIFF_SSTL18_I",
                "DIFF_SSTL18_II",
                "BLVDS_25",
            }
            if "i" in pin.dir:
                if self.family in XFDDR_FAMILIES:
                    # First-generation input DDR register: basically just two FFs with opposite
                    # clocks. Add a register on both outputs, so that they enter fabric on
                    # the same clock edge, adding one cycle of latency.
                    i0_ff = Signal.like(pin_i0, name_suffix="_ff")
                    i1_ff = Signal.like(pin_i1, name_suffix="_ff")
                    get_dff(pin.i_clk, i0_ff, pin_i0)
                    get_dff(pin.i_clk, i1_ff, pin_i1)
                    get_iob_dff(pin.i_clk, i, i0_ff)
                    get_iob_dff(~pin.i_clk, i, i1_ff)
                elif self.family in XDDR2_FAMILIES:
                    if self.family == 'spartan6' or iostd in DIFF_S3EA:
                        # Second-generation input DDR register: hw realigns i1 to positive clock edge,
                        # but also misaligns it with i0 input.  Re-register first input before it
                        # enters fabric. This allows both inputs to enter fabric on the same clock
                        # edge, and adds one cycle of latency.
                        i0_ff = Signal.like(pin_i0, name_suffix="_ff")
                        get_dff(pin.i_clk, i0_ff, pin_i0)
                        get_iddr2(pin.i_clk, i, i0_ff, pin_i1, "C0")
                    else:
                        # No extra register available for hw alignment, use extra registers.
                        i0_ff = Signal.like(pin_i0, name_suffix="_ff")
                        i1_ff = Signal.like(pin_i1, name_suffix="_ff")
                        get_dff(pin.i_clk, i0_ff, pin_i0)
                        get_dff(pin.i_clk, i1_ff, pin_i1)
                        get_iddr2(pin.i_clk, i, i0_ff, i1_ff, "NONE")
                else:
                    # Third-generation input DDR register: does all of the above on its own.
                    get_iddr(pin.i_clk, i, pin_i0, pin_i1)
            if "o" in pin.dir:
                if self.family in XFDDR_FAMILIES or self.family == "spartan3e" or (self.family.startswith("spartan3a") and iostd not in TRUE_DIFF_S3EA):
                    # For this generation, we need to realign o1 input ourselves.
                    o1_ff = Signal.like(pin_o1, name_suffix="_ff")
                    get_dff(pin.o_clk, pin_o1, o1_ff)
                    get_fddr(pin.o_clk, pin_o0, o1_ff, o)
                else:
                    get_oddr(pin.o_clk, pin_o0, pin_o1, o)
            if pin.dir in ("oe", "io"):
                if self.family == "spartan6":
                    get_oddr(pin.o_clk, ~pin.oe, ~pin.oe, t)
                else:
                    get_iob_dff(pin.o_clk, ~pin.oe, t)
        else:
            assert False

        return (i, o, t)

    def _get_valid_xdrs(self):
        if self.family in {"virtex", "virtexe"}:
            return (0, 1)
        else:
            return (0, 1, 2)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF",
                i_I=port.io[bit],
                o_O=i[bit]
            )
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert)
        if self.vendor_toolchain:
            for bit in range(pin.width):
                m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF",
                    i_I=o[bit],
                    o_O=port.io[bit]
                )
        else:
            m.d.comb += port.eq(self._invert_if(invert, o))
        return m

    def get_tristate(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_tristate(pin, port, attrs, invert)

        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFT",
                i_T=t,
                i_I=o[bit],
                o_O=port.io[bit]
            )
        return m

    def get_input_output(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_input_output(pin, port, attrs, invert)

        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD"), i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF",
                i_T=t,
                i_I=o[bit],
                o_O=i[bit],
                io_IO=port.io[bit]
            )
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_diff_input(pin, port, attrs, invert)

        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUFDS",
                i_I=port.p[bit], i_IB=port.n[bit],
                o_O=i[bit]
            )
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_diff_output(pin, port, attrs, invert)

        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFDS",
                i_I=o[bit],
                o_O=port.p[bit], o_OB=port.n[bit]
            )
        return m

    def get_diff_tristate(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_diff_tristate(pin, port, attrs, invert)

        self._check_feature("differential tristate", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUFTDS",
                i_T=t,
                i_I=o[bit],
                o_O=port.p[bit], o_OB=port.n[bit]
            )
        return m

    def get_diff_input_output(self, pin, port, attrs, invert):
        if not self.vendor_toolchain:
            return super().get_diff_input_output(pin, port, attrs, invert)

        self._check_feature("differential input/output", pin, attrs,
                            valid_xdrs=self._get_valid_xdrs(), valid_attrs=True)
        m = Module()
        i, o, t = self._get_xdr_buffer(m, pin, attrs.get("IOSTANDARD", "LVDS_25"), i_invert=invert, o_invert=invert)
        for bit in range(pin.width):
            m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUFDS",
                i_T=t,
                i_I=o[bit],
                o_O=i[bit],
                io_IO=port.p[bit], io_IOB=port.n[bit]
            )
        return m

    # The synchronizer implementations below apply two separate but related timing constraints.
    #
    # First, the ASYNC_REG attribute prevents inference of shift registers from synchronizer FFs,
    # and constraints the FFs to be placed as close as possible, ideally in one CLB. This attribute
    # only affects the synchronizer FFs themselves.
    #
    # Second, for Vivado only, the amaranth.vivado.false_path or amaranth.vivado.max_delay attribute
    # affects the path into the synchronizer. If maximum input delay is specified, a datapath-only
    # maximum delay constraint is applied, limiting routing delay (and therefore skew) at
    # the synchronizer input.  Otherwise, a false path constraint is used to omit the input path
    # from the timing analysis.

    def get_ff_sync(self, ff_sync):
        m = Module()
        flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index),
                        reset=ff_sync._reset, reset_less=ff_sync._reset_less,
                        attrs={"ASYNC_REG": "TRUE"})
                 for index in range(ff_sync._stages)]
        if self.toolchain == "Vivado":
            if ff_sync._max_input_delay is None:
                flops[0].attrs["amaranth.vivado.false_path"] = "TRUE"
            else:
                flops[0].attrs["amaranth.vivado.max_delay"] = str(ff_sync._max_input_delay * 1e9)
        elif ff_sync._max_input_delay is not None:
            raise NotImplementedError("Platform '{}' does not support constraining input delay "
                                      "for FFSynchronizer"
                                      .format(type(self).__name__))
        for i, o in zip((ff_sync.i, *flops), flops):
            m.d[ff_sync._o_domain] += o.eq(i)
        m.d.comb += ff_sync.o.eq(flops[-1])
        return m


    def get_async_ff_sync(self, async_ff_sync):
        m = Module()
        m.domains += ClockDomain("async_ff", async_reset=True, local=True)
        flops = [Signal(1, name="stage{}".format(index), reset=1,
                        attrs={"ASYNC_REG": "TRUE"})
                 for index in range(async_ff_sync._stages)]
        if self.toolchain == "Vivado":
            if async_ff_sync._max_input_delay is None:
                flops[0].attrs["amaranth.vivado.false_path"] = "TRUE"
            else:
                flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9)
        elif async_ff_sync._max_input_delay is not None:
            raise NotImplementedError("Platform '{}' does not support constraining input delay "
                                      "for AsyncFFSynchronizer"
                                      .format(type(self).__name__))
        for i, o in zip((0, *flops), flops):
            m.d.async_ff += o.eq(i)

        if async_ff_sync._edge == "pos":
            m.d.comb += ResetSignal("async_ff").eq(async_ff_sync.i)
        else:
            m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i)

        m.d.comb += [
            ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)),
            async_ff_sync.o.eq(flops[-1])
        ]

        return m


# amaranth/vendor/_lattice_ice40.py
from abc import abstractmethod

from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *


class LatticeICE40Platform(TemplatedPlatform):
    """
    .. rubric:: IceStorm toolchain

    Required tools:
        * ``yosys``
        * ``nextpnr-ice40``
        * ``icepack``

    The environment is populated by running the script specified in the environment variable
    ``AMARANTH_ENV_ICESTORM``, if present.

    Available overrides:
        * ``verbose``: enables logging of informational messages to standard error.
        * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
        * ``synth_opts``: adds options for ``synth_ice40`` Yosys command.
        * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
        * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
        * ``yosys_opts``: adds extra options for ``yosys``.
        * ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``.
        * ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
        * ``add_constraints``: inserts commands at the end in the PCF file.

    Build products:
        * ``{{name}}.rpt``: Yosys log.
        * ``{{name}}.json``: synthesized RTL.
        * ``{{name}}.tim``: nextpnr log.
        * ``{{name}}.asc``: ASCII bitstream.
        * ``{{name}}.bin``: binary bitstream.

    .. rubric:: iCECube2 toolchain

    This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.

    Required tools:
        * iCECube2 toolchain
        * ``tclsh``

    The environment is populated by setting the necessary environment variables based on
    ``AMARANTH_ENV_ICECUBE2``, which must point to the root of the iCECube2 installation, and
    is required.

    Available overrides:
        * ``verbose``: enables logging of informational messages to standard error.
        * ``lse_opts``: adds options for LSE.
        * ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
        * ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
        * ``add_constraints``: inserts commands in SDC file.
        * ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
          Tcl script.

    Build products:
        * ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
        * ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
        * ``{{name}}.edf``: EDIF netlist.
        * ``{{name}}.bin``: binary bitstream.
    """

    toolchain = None # selected when creating platform

    device  = property(abstractmethod(lambda: None))
    package = property(abstractmethod(lambda: None))

    # IceStorm templates

    _nextpnr_device_options = {
        "iCE40LP384": "--lp384",
        "iCE40LP1K":  "--lp1k",
        "iCE40LP4K":  "--lp8k",
        "iCE40LP8K":  "--lp8k",
        "iCE40HX1K":  "--hx1k",
        "iCE40HX4K":  "--hx8k",
        "iCE40HX8K":  "--hx8k",
        "iCE40UP5K":  "--up5k",
        "iCE40UP3K":  "--up5k",
        "iCE5LP4K":   "--u4k",
        "iCE5LP2K":   "--u4k",
        "iCE5LP1K":   "--u4k",
    }
    _nextpnr_package_options = {
        "iCE40LP4K":  ":4k",
        "iCE40HX4K":  ":4k",
        "iCE40UP3K":  "",
        "iCE5LP2K":   "",
        "iCE5LP1K":   "",
    }

    _icestorm_required_tools = [
        "yosys",
        "nextpnr-ice40",
        "icepack",
    ]
    _icestorm_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "{{name}}.il": r"""
            # {{autogenerated}}
            {{emit_rtlil()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}.ys": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v") -%}
                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".sv") -%}
                read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
            {% endfor %}
            {% for file in platform.iter_files(".il") -%}
                read_ilang {{file}}
            {% endfor %}
            read_ilang {{name}}.il
            delete w:$verilog_initial_trigger
            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
            synth_ice40 {{get_override("synth_opts")|options}} -top {{name}}
            {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
            write_json {{name}}.json
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}}
            {% endfor%}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
    }
    _icestorm_command_templates = [
        r"""
        {{invoke_tool("yosys")}}
            {{quiet("-q")}}
            {{get_override("yosys_opts")|options}}
            -l {{name}}.rpt
            {{name}}.ys
        """,
        r"""
        {{invoke_tool("nextpnr-ice40")}}
            {{quiet("--quiet")}}
            {{get_override("nextpnr_opts")|options}}
            --log {{name}}.tim
            {{platform._nextpnr_device_options[platform.device]}}
            --package
                {{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]|
                                            default("")}}
            --json {{name}}.json
            --pcf {{name}}.pcf
            --asc {{name}}.asc
        """,
        r"""
        {{invoke_tool("icepack")}}
            {{verbose("-v")}}
            {{name}}.asc
            {{name}}.bin
        """
    ]

    # iCECube2 templates

    _icecube2_required_tools = [
        "synthesis",
        "synpwrap",
        "tclsh",
    ]
    _icecube2_file_templates = {
        **TemplatedPlatform.build_script_templates,
        "build_{{name}}.sh": r"""
            # {{autogenerated}}
            set -e{{verbose("x")}}
            {% for var in platform._all_toolchain_env_vars %}
            if [ -n "${{var}}" ]; then
                # LSE environment
                export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
                export PATH=${{var}}/LSE/bin/lin64:$PATH
                export FOUNDRY=${{var}}/LSE
                # Synplify environment
                export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
                export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
                export SYNPLIFY_PATH=${{var}}/synpbase
                # Common environment
                export SBT_DIR=${{var}}/sbt_backend
            else
                echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
            fi
            {% endfor %}
            {{emit_commands("sh")}}
        """,
        "{{name}}.v": r"""
            /* {{autogenerated}} */
            {{emit_verilog()}}
        """,
        "{{name}}.debug.v": r"""
            /* {{autogenerated}} */
            {{emit_debug_verilog()}}
        """,
        "{{name}}_lse.prj": r"""
            # {{autogenerated}}
            -a SBT{{platform.family}}
            -d {{platform.device}}
            -t {{platform.package}}
            {{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
            {% for file in platform.iter_files(".v") -%}
                -ver {{file}}
            {% endfor %}
            -ver {{name}}.v
            -sdc {{name}}.sdc
            -top {{name}}
            -output_edif {{name}}.edf
            -logfile {{name}}_lse.log
        """,
        "{{name}}_syn.prj": r"""
            # {{autogenerated}}
            {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
                add_file -verilog {{file|tcl_escape}}
            {% endfor %}
            add_file -verilog {{name}}.v
            add_file -constraint {{name}}.sdc
            {{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
            impl -add {{name}}_design -type fpga
            set_option -technology SBT{{platform.family}}
            set_option -part {{platform.device}}
            set_option -package {{platform.package}}
            {{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
            project -result_format edif
            project -result_file {{name}}.edf
            impl -active {{name}}_design
            project -run compile
            project -run map
            project -run fpga_mapper
            file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
        """,
        "{{name}}.sdc": r"""
            # {{autogenerated}}
            {% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
                {% if port_signal is not none -%}
                    create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
                {% else -%}
                    create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
                {% endif %}
            {% endfor %}
            {{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
        """,
        "{{name}}.tcl": r"""
            # {{autogenerated}}
            set device {{platform.device}}-{{platform.package}}
            set top_module {{name}}
            set proj_dir .
            set output_dir .
            set edif_file {{name}}
            set tool_options ":edifparser -y {{name}}.pcf"
            set sbt_root $::env(SBT_DIR)
            append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
            source $sbt_tcl
            run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
            {{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
            file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
            exit
        """,
        "{{name}}.pcf": r"""
            # {{autogenerated}}
            {% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
                set_io {{port_name}} {{pin_name}}
            {% endfor %}
        """,
    }
    _lse_icecube2_command_templates = [
        r"""synthesis -f {{name}}_lse.prj""",
        r"""tclsh {{name}}.tcl""",
    ]
    _synplify_icecube2_command_templates = [
        r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
        r"""tclsh {{name}}.tcl""",
    ]

    # Common logic

    def __init__(self, *, toolchain="IceStorm"):
        super().__init__()

        assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
        self.toolchain = toolchain

    @property
    def family(self):
        if self.device.startswith("iCE40"):
            return "iCE40"
        if self.device.startswith("iCE5"):
            return "iCE5"
        assert False

    @property
    def _toolchain_env_var(self):
        if self.toolchain == "IceStorm":
            return f"AMARANTH_ENV_{self.toolchain}"
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return f"AMARANTH_ENV_ICECUBE2"
        assert False

    @property
    def required_tools(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_required_tools
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return self._icecube2_required_tools
        assert False

    @property
    def file_templates(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_file_templates
        if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
            return self._icecube2_file_templates
        assert False

    @property
    def command_templates(self):
        if self.toolchain == "IceStorm":
            return self._icestorm_command_templates
        if self.toolchain == "LSE-iCECube2":
            return self._lse_icecube2_command_templates
        if self.toolchain == "Synplify-iCECube2":
            return self._synplify_icecube2_command_templates
        assert False

    @property
    def default_clk_constraint(self):
        # Internal high-speed oscillator: 48 MHz / (2 ^ div)
        if self.default_clk == "SB_HFOSC":
            return Clock(48e6 / 2 ** self.hfosc_div)
        # Internal low-speed oscillator: 10 KHz
        elif self.default_clk == "SB_LFOSC":
            return Clock(10e3)
        # Otherwise, use the defined Clock resource.
        return super().default_clk_constraint

    def create_missing_domain(self, name):
        # For unknown reasons (no errata was ever published, and no documentation mentions this
        # issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
        # global reset. Note that this is a *time-based* delay, generated purely by the internal
        # oscillator, which may not be observed nor influenced directly. For details, see links:
        #  * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
        #  * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
        #
        # To handle this, it is necessary to have a global reset in any iCE40 design that may
        # potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
        # (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
        # reset line, this line is ORed with the power on reset.
        #
        # If an internal oscillator is selected as the default clock source, the power-on-reset
        # delay is increased to 100 us, since the oscillators are only stable after that long.
        #
        # The power-on reset timer counts up because the vendor tools do not support initialization
        # of flip-flops.
        if name == "sync" and self.default_clk is not None:
            m = Module()

            # Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider.
            if self.default_clk == "SB_HFOSC":
                if not hasattr(self, "hfosc_div"):
                    raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
                                     "between 0 and 3")
                if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3:
                    raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
                                     "between 0 and 3, not {!r}"
                                     .format(self.hfosc_div))
                clk_i = Signal()
                m.submodules += Instance("SB_HFOSC",
                                         i_CLKHFEN=1,
                                         i_CLKHFPU=1,
                                         p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div),
                                         o_CLKHF=clk_i)
                delay = int(100e-6 * self.default_clk_frequency)
            # Internal low-speed clock: 10 KHz.
            elif self.default_clk == "SB_LFOSC":
                clk_i = Signal()
                m.submodules += Instance("SB_LFOSC",
                                         i_CLKLFEN=1,
                                         i_CLKLFPU=1,
                                         o_CLKLF=clk_i)
                delay = int(100e-6 * self.default_clk_frequency)
            # User-defined clock signal.
            else:
                clk_i = self.request(self.default_clk).i
                delay = int(15e-6 * self.default_clk_frequency)

            if self.default_rst is not None:
                rst_i = self.request(self.default_rst).i
            else:
                rst_i = Const(0)

            # Power-on-reset domain
            m.domains += ClockDomain("por", reset_less=True, local=True)
            timer = Signal(range(delay))
            ready = Signal()
            m.d.comb += ClockSignal("por").eq(clk_i)
            with m.If(timer == delay):
                m.d.por += ready.eq(1)
            with m.Else():
                m.d.por += timer.eq(timer + 1)

            # Primary domain
            m.domains += ClockDomain("sync")
            m.d.comb += ClockSignal("sync").eq(clk_i)
            if self.default_rst is not None:
                m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync")
            else:
                m.d.comb += ResetSignal("sync").eq(~ready)

            return m

    def should_skip_port_component(self, port, attrs, component):
        # On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
        # the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
        # between LP/HX and UP series:
        #  * for LP/HX, z=0 is DPxxB   (B is non-inverting, A is inverting)
        #  * for UP,    z=0 is IOB_xxA (A is non-inverting, B is inverting)
        if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n":
            return True
        return False

    def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False,
                       invert_lut=False):
        def get_dff(clk, d, q):
            m.submodules += Instance("$dff",
                p_CLK_POLARITY=1,
                p_WIDTH=len(d),
                i_CLK=clk,
                i_D=d,
                o_Q=q)

        def get_ineg(y, invert):
            if invert_lut:
                a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0))
                for bit in range(len(y)):
                    m.submodules += Instance("SB_LUT4",
                        p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
                        i_I0=a[bit],
                        i_I1=Const(0),
                        i_I2=Const(0),
                        i_I3=Const(0),
                        o_O=y[bit])
                return a
            elif invert:
                a = Signal.like(y, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return a
            else:
                return y

        def get_oneg(a, invert):
            if invert_lut:
                y = Signal.like(a, name_suffix="_x{}".format(1 if invert else 0))
                for bit in range(len(a)):
                    m.submodules += Instance("SB_LUT4",
                        p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
                        i_I0=a[bit],
                        i_I1=Const(0),
                        i_I2=Const(0),
                        i_I3=Const(0),
                        o_O=y[bit])
                return y
            elif invert:
                y = Signal.like(a, name_suffix="_n")
                m.d.comb += y.eq(~a)
                return y
            else:
                return a

        if "GLOBAL" in attrs:
            is_global_input = bool(attrs["GLOBAL"])
            del attrs["GLOBAL"]
        else:
            is_global_input = False
        assert not (is_global_input and i_invert)

        if "i" in pin.dir:
            if pin.xdr < 2:
                pin_i  = get_ineg(pin.i,  i_invert)
            elif pin.xdr == 2:
                pin_i0 = get_ineg(pin.i0, i_invert)
                pin_i1 = get_ineg(pin.i1, i_invert)
        if "o" in pin.dir:
            if pin.xdr < 2:
                pin_o  = get_oneg(pin.o,  o_invert)
            elif pin.xdr == 2:
                pin_o0 = get_oneg(pin.o0, o_invert)
                pin_o1 = get_oneg(pin.o1, o_invert)

        if "i" in pin.dir and pin.xdr == 2:
            i0_ff = Signal.like(pin_i0, name_suffix="_ff")
            i1_ff = Signal.like(pin_i1, name_suffix="_ff")
            get_dff(pin.i_clk, i0_ff, pin_i0)
            get_dff(pin.i_clk, i1_ff, pin_i1)
        if "o" in pin.dir and pin.xdr == 2:
            o1_ff = Signal.like(pin_o1, name_suffix="_ff")
            get_dff(pin.o_clk, pin_o1, o1_ff)

        for bit in range(len(port)):
            io_args = [
                ("io", "PACKAGE_PIN", port[bit]),
                *(("p", key, value) for key, value in attrs.items()),
            ]

            if "i" not in pin.dir:
                # If no input pin is requested, it is important to use a non-registered input pin
                # type, because an output-only pin would not have an input clock, and if its input
                # is configured as registered, this would prevent a co-located input-capable pin
                # from using an input clock.
                i_type =     0b01 # PIN_INPUT
            elif pin.xdr == 0:
                i_type =     0b01 # PIN_INPUT
            elif pin.xdr > 0:
                i_type =     0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
            if "o" not in pin.dir:
                o_type = 0b0000   # PIN_NO_OUTPUT
            elif pin.xdr == 0 and pin.dir == "o":
                o_type = 0b0110   # PIN_OUTPUT
            elif pin.xdr == 0:
                o_type = 0b1010   # PIN_OUTPUT_TRISTATE
            elif pin.xdr == 1 and pin.dir == "o":
                o_type = 0b0101   # PIN_OUTPUT_REGISTERED
            elif pin.xdr == 1:
                o_type = 0b1101   # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
            elif pin.xdr == 2 and pin.dir == "o":
                o_type = 0b0100   # PIN_OUTPUT_DDR
            elif pin.xdr == 2:
                o_type = 0b1100   # PIN_OUTPUT_DDR_ENABLE_REGISTERED
            io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))

            if hasattr(pin, "i_clk"):
                io_args.append(("i", "INPUT_CLK",  pin.i_clk))
            if hasattr(pin, "o_clk"):
                io_args.append(("i", "OUTPUT_CLK", pin.o_clk))

            if "i" in pin.dir:
                if pin.xdr == 0 and is_global_input:
                    io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit]))
                elif pin.xdr < 2:
                    io_args.append(("o", "D_IN_0",  pin_i[bit]))
                elif pin.xdr == 2:
                    # Re-register both inputs before they enter fabric. This increases hold time
                    # to an entire cycle, and adds one cycle of latency.
                    io_args.append(("o", "D_IN_0",  i0_ff[bit]))
                    io_args.append(("o", "D_IN_1",  i1_ff[bit]))
            if "o" in pin.dir:
                if pin.xdr < 2:
                    io_args.append(("i", "D_OUT_0", pin_o[bit]))
                elif pin.xdr == 2:
                    # Re-register negedge output after it leaves fabric. This increases setup time
                    # to an entire cycle, and doesn't add latency.
                    io_args.append(("i", "D_OUT_0", pin_o0[bit]))
                    io_args.append(("i", "D_OUT_1", o1_ff[bit]))

            if pin.dir in ("oe", "io"):
                io_args.append(("i", "OUTPUT_ENABLE", pin.oe))

            if is_global_input:
                m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args)
            else:
                m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_IO", *io_args)

    def get_input(self, pin, port, attrs, invert):
        self._check_feature("single-ended input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert)
        return m

    def get_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
        return m

    def get_tristate(self, pin, port, attrs, invert):
        self._check_feature("single-ended tristate", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
        return m

    def get_input_output(self, pin, port, attrs, invert):
        self._check_feature("single-ended input/output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert)
        return m

    def get_diff_input(self, pin, port, attrs, invert):
        self._check_feature("differential input", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        # See comment in should_skip_port_component above.
        self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert)
        return m

    def get_diff_output(self, pin, port, attrs, invert):
        self._check_feature("differential output", pin, attrs,
                            valid_xdrs=(0, 1, 2), valid_attrs=True)
        m = Module()
        # Note that the non-inverting output pin is not driven the same way as a regular
        # output pin. The inverter introduces a delay, so for a non-inverting output pin,
        # an identical delay is introduced by instantiating a LUT. This makes the waveform
        # perfectly symmetric in the xdr=0 case.
        self._get_io_buffer(m, pin, port.p, attrs, o_invert=    invert, invert_lut=True)
        self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True)
        return m

    # Tristate bidirectional buffers are not supported on iCE40 because it requires external
    # termination, which is different for differential pins configured as inputs and outputs.

    # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
    # the necessary attributes; nextpnr-ice40 does not.
