lib.io: Implement *Buffer
from RFC 55.
This commit is contained in:
parent
81eae1dd35
commit
456dcaeb7b
|
@ -1,13 +1,19 @@
|
|||
import enum
|
||||
import operator
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import Iterable
|
||||
|
||||
from ..hdl import *
|
||||
from ..lib import wiring
|
||||
from ..lib import wiring, data
|
||||
from ..lib.wiring import In, Out
|
||||
from .. import tracer
|
||||
|
||||
|
||||
__all__ = ["Direction", "SingleEndedPort", "DifferentialPort", "Pin"]
|
||||
__all__ = [
|
||||
"Direction", "PortLike", "SingleEndedPort", "DifferentialPort",
|
||||
"Buffer", "FFBuffer", "DDRBuffer",
|
||||
"Pin",
|
||||
]
|
||||
|
||||
|
||||
class Direction(enum.Enum):
|
||||
|
@ -41,16 +47,52 @@ class Direction(enum.Enum):
|
|||
raise ValueError("Cannot combine input port with output port")
|
||||
|
||||
|
||||
class SingleEndedPort:
|
||||
class PortLike(metaclass=ABCMeta):
|
||||
"""Represents an abstract port that can be passed to a buffer.
|
||||
|
||||
The port types supported by most platforms are :class:`SingleEndedPort` and
|
||||
:class:`DifferentialPort`. Platforms may define additional custom port types as appropriate.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def direction(self):
|
||||
"""The direction of this port, as :class:`Direction`."""
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
@abstractmethod
|
||||
def __len__(self):
|
||||
"""Returns the width of this port in bits."""
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
@abstractmethod
|
||||
def __getitem__(self, index):
|
||||
"""Slices the port, returning another :class:`PortLike` with a subset
|
||||
of its bits.
|
||||
|
||||
The index can be a :class:`slice` or :class:`int`. If the index is
|
||||
an :class:`int`, the result is a single-bit :class:`PortLike`."""
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
@abstractmethod
|
||||
def __invert__(self):
|
||||
"""Returns a new :class:`PortLike` object like this one, but with inverted polarity.
|
||||
|
||||
The result should be such that using :class:`Buffer` on it is equivalent to using
|
||||
:class:`Buffer` on the original, with added inverters on the :py:`i` and :py:`o` ports."""
|
||||
raise NotImplementedError # :nocov:
|
||||
|
||||
|
||||
class SingleEndedPort(PortLike):
|
||||
"""Represents a single-ended I/O port with optional inversion.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
io : IOValue
|
||||
io : :class:`IOValue`
|
||||
The raw I/O value being wrapped.
|
||||
invert : bool or iterable of bool
|
||||
invert : :class:`bool` or iterable of :class:`bool`
|
||||
If true, the electrical state of the physical pin will be opposite from the Amaranth value
|
||||
(the ``*Buffer`` classes will insert inverters on ``o`` and ``i`` pins, as appropriate).
|
||||
(the ``*Buffer`` classes will insert inverters on :py:`o` and :py:`i` pins, as appropriate).
|
||||
|
||||
This can be used for various purposes:
|
||||
|
||||
|
@ -59,9 +101,9 @@ class SingleEndedPort:
|
|||
on the pin
|
||||
|
||||
If the value is a simple :class:`bool`, it is used for all bits of this port. If the value
|
||||
is an iterable of :class:`bool`, the iterable must have the same length as ``io``, and
|
||||
is an iterable of :class:`bool`, the iterable must have the same length as :py:`io`, and
|
||||
the inversion is specified per-bit.
|
||||
direction : Direction or str
|
||||
direction : :class:`Direction` or :class:`str`
|
||||
Represents the allowed directions of this port. If equal to :attr:`Direction.Input` or
|
||||
:attr:`Direction.Output`, this port can only be used with buffers of matching direction.
|
||||
If equal to :attr:`Direction.Bidir`, this port can be used with buffers of any direction.
|
||||
|
@ -84,18 +126,18 @@ class SingleEndedPort:
|
|||
|
||||
@property
|
||||
def io(self):
|
||||
"""The ``io`` argument passed to the constructor."""
|
||||
"""The :py:`io` argument passed to the constructor."""
|
||||
return self._io
|
||||
|
||||
@property
|
||||
def invert(self):
|
||||
"""The ``invert`` argument passed to the constructor, normalized to a :class:`tuple`
|
||||
"""The :py:`invert` argument passed to the constructor, normalized to a :class:`tuple`
|
||||
of :class:`bool`."""
|
||||
return self._invert
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
"""The ``direction`` argument passed to the constructor, normalized to :class:`Direction`."""
|
||||
"""The :py:`direction` argument passed to the constructor, normalized to :class:`Direction`."""
|
||||
return self._direction
|
||||
|
||||
def __len__(self):
|
||||
|
@ -103,7 +145,7 @@ class SingleEndedPort:
|
|||
return len(self._io)
|
||||
|
||||
def __invert__(self):
|
||||
"""Returns a new :class:`SingleEndedPort` with the opposite value of ``invert``."""
|
||||
"""Returns a new :class:`SingleEndedPort` with the opposite value of :py:`invert`."""
|
||||
return SingleEndedPort(self._io, invert=tuple(not inv for inv in self._invert),
|
||||
direction=self._direction)
|
||||
|
||||
|
@ -141,19 +183,19 @@ class SingleEndedPort:
|
|||
return f"SingleEndedPort({self._io!r}, invert={invert!r}, direction={self._direction})"
|
||||
|
||||
|
||||
class DifferentialPort:
|
||||
class DifferentialPort(PortLike):
|
||||
"""Represents a differential I/O port with optional inversion.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
p : IOValue
|
||||
p : :class:`IOValue`
|
||||
The raw I/O value used as positive (true) half of the port.
|
||||
n : IOValue
|
||||
n : :class:`IOValue`
|
||||
The raw I/O value used as negative (complemented) half of the port. Must have the same
|
||||
length as ``p``.
|
||||
invert : bool or iterable of bool
|
||||
length as :py:`p`.
|
||||
invert : :class:`bool` or iterable of :class`bool`
|
||||
If true, the electrical state of the physical pin will be opposite from the Amaranth value
|
||||
(the ``*Buffer`` classes will insert inverters on ``o`` and ``i`` pins, as appropriate).
|
||||
(the ``*Buffer`` classes will insert inverters on :py:`o` and :py:`i` pins, as appropriate).
|
||||
|
||||
This can be used for various purposes:
|
||||
|
||||
|
@ -161,9 +203,9 @@ class DifferentialPort:
|
|||
- Compensating for boards where the P and N pins are swapped (e.g. for easier routing)
|
||||
|
||||
If the value is a simple :class:`bool`, it is used for all bits of this port. If the value
|
||||
is an iterable of :class:`bool`, the iterable must have the same length as ``io``, and
|
||||
is an iterable of :class:`bool`, the iterable must have the same length as :py:`io`, and
|
||||
the inversion is specified per-bit.
|
||||
direction : Direction or str
|
||||
direction : :class:`Direction` or :class:`str`
|
||||
Represents the allowed directions of this port. If equal to :attr:`Direction.Input` or
|
||||
:attr:`Direction.Output`, this port can only be used with buffers of matching direction.
|
||||
If equal to :attr:`Direction.Bidir`, this port can be used with buffers of any direction.
|
||||
|
@ -190,23 +232,23 @@ class DifferentialPort:
|
|||
|
||||
@property
|
||||
def p(self):
|
||||
"""The ``p`` argument passed to the constructor."""
|
||||
"""The :py:`p` argument passed to the constructor."""
|
||||
return self._p
|
||||
|
||||
@property
|
||||
def n(self):
|
||||
"""The ``n`` argument passed to the constructor."""
|
||||
"""The :py:`n` argument passed to the constructor."""
|
||||
return self._n
|
||||
|
||||
@property
|
||||
def invert(self):
|
||||
"""The ``invert`` argument passed to the constructor, normalized to a :class:`tuple`
|
||||
"""The :py:`invert` argument passed to the constructor, normalized to a :class:`tuple`
|
||||
of :class:`bool`."""
|
||||
return self._invert
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
"""The ``direction`` argument passed to the constructor, normalized to :class:`Direction`."""
|
||||
"""The :py:`direction` argument passed to the constructor, normalized to :class:`Direction`."""
|
||||
return self._direction
|
||||
|
||||
def __len__(self):
|
||||
|
@ -214,7 +256,7 @@ class DifferentialPort:
|
|||
return len(self._p)
|
||||
|
||||
def __invert__(self):
|
||||
"""Returns a new :class:`DifferentialPort` with the opposite value of ``invert``."""
|
||||
"""Returns a new :class:`DifferentialPort` with the opposite value of :py:`invert`."""
|
||||
return DifferentialPort(self._p, self._n, invert=tuple(not inv for inv in self._invert),
|
||||
direction=self._direction)
|
||||
|
||||
|
@ -253,6 +295,357 @@ class DifferentialPort:
|
|||
return f"DifferentialPort({self._p!r}, {self._n!r}, invert={invert!r}, direction={self._direction})"
|
||||
|
||||
|
||||
class Buffer(wiring.Component):
|
||||
"""A combinatorial I/O buffer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
port : :class:`PortLike`
|
||||
|
||||
Attributes
|
||||
----------
|
||||
signature : :class:`Buffer.Signature`
|
||||
Created based on constructor arguments.
|
||||
"""
|
||||
class Signature(wiring.Signature):
|
||||
"""A signature of a combinatorial I/O buffer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
width : :class:`int`
|
||||
|
||||
Attributes
|
||||
----------
|
||||
i: :py:`unsigned(width)` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
|
||||
o: :py:`unsigned(width)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
|
||||
oe: :py:`unsigned(1, init=0)` (if :py:`direction is Direction.Bidir`)
|
||||
oe: :py:`unsigned(1, init=1)` (if :py:`direction is Direction.Output`)
|
||||
"""
|
||||
def __init__(self, direction, width):
|
||||
self._direction = Direction(direction)
|
||||
self._width = operator.index(width)
|
||||
members = {}
|
||||
if self._direction is not Direction.Output:
|
||||
members["i"] = wiring.In(self._width)
|
||||
if self._direction is not Direction.Input:
|
||||
members["o"] = wiring.Out(self._width)
|
||||
members["oe"] = wiring.Out(1, init=int(self._direction is Direction.Output))
|
||||
super().__init__(members)
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self._direction
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.direction == other.direction and self.width == other.width
|
||||
|
||||
def __repr__(self):
|
||||
return f"Buffer.Signature({self.direction}, {self.width})"
|
||||
|
||||
def __init__(self, direction, port):
|
||||
if not isinstance(port, PortLike):
|
||||
raise TypeError(f"'port' must be a 'PortLike', not {port!r}")
|
||||
self._port = port
|
||||
super().__init__(Buffer.Signature(direction, len(port)).flip())
|
||||
if port.direction is Direction.Input and self.direction is not Direction.Input:
|
||||
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
|
||||
if port.direction is Direction.Output and self.direction is not Direction.Output:
|
||||
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self.signature.direction
|
||||
|
||||
def elaborate(self, platform):
|
||||
if hasattr(platform, "get_io_buffer"):
|
||||
return platform.get_io_buffer(self)
|
||||
|
||||
m = Module()
|
||||
|
||||
invert = sum(bit << idx for idx, bit in enumerate(self._port.invert))
|
||||
if self.direction is not Direction.Input:
|
||||
if invert != 0:
|
||||
o_inv = Signal.like(self.o)
|
||||
m.d.comb += o_inv.eq(self.o ^ invert)
|
||||
else:
|
||||
o_inv = self.o
|
||||
if self.direction is not Direction.Output:
|
||||
if invert:
|
||||
i_inv = Signal.like(self.i)
|
||||
m.d.comb += self.i.eq(i_inv ^ invert)
|
||||
else:
|
||||
i_inv = self.i
|
||||
|
||||
if isinstance(self._port, SingleEndedPort):
|
||||
if self.direction is Direction.Input:
|
||||
m.submodules += IOBufferInstance(self._port.io, i=i_inv)
|
||||
elif self.direction is Direction.Output:
|
||||
m.submodules += IOBufferInstance(self._port.io, o=o_inv, oe=self.oe)
|
||||
else:
|
||||
m.submodules += IOBufferInstance(self._port.io, o=o_inv, oe=self.oe, i=i_inv)
|
||||
elif isinstance(self._port, DifferentialPort):
|
||||
if self.direction is Direction.Input:
|
||||
m.submodules += IOBufferInstance(self._port.p, i=i_inv)
|
||||
elif self.direction is Direction.Output:
|
||||
m.submodules += IOBufferInstance(self._port.p, o=o_inv, oe=self.oe)
|
||||
m.submodules += IOBufferInstance(self._port.n, o=~o_inv, oe=self.oe)
|
||||
else:
|
||||
m.submodules += IOBufferInstance(self._port.p, o=o_inv, oe=self.oe, i=i_inv)
|
||||
m.submodules += IOBufferInstance(self._port.n, o=~o_inv, oe=self.oe)
|
||||
else:
|
||||
raise TypeError("Cannot elaborate generic 'Buffer' with port {self._port!r}")
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class FFBuffer(wiring.Component):
|
||||
"""A registered I/O buffer.
|
||||
|
||||
Equivalent to a plain :class:`Buffer` combined with reset-less registers on :py:`i`, :py:`o`,
|
||||
:py:`oe`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
port : :class:`PortLike`
|
||||
i_domain : :class:`str`
|
||||
Domain for input register. Only used when :py:`direction in (Direction.Input, Direction.Bidir)`.
|
||||
Defaults to :py:`"sync"`
|
||||
o_domain : :class:`str`
|
||||
Domain for output and output enable registers. Only used when
|
||||
:py:`direction in (Direction.Output, Direction.Bidir)`. Defaults to :py:`"sync"`
|
||||
|
||||
Attributes
|
||||
----------
|
||||
signature : FFBuffer.Signature
|
||||
Created based on constructor arguments.
|
||||
"""
|
||||
class Signature(wiring.Signature):
|
||||
"""A signature of a registered I/O buffer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
width : :class:`int`
|
||||
|
||||
Attributes
|
||||
----------
|
||||
i: :py:`unsigned(width)` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
|
||||
o: :py:`unsigned(width)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
|
||||
oe: :py:`unsigned(1, init=0)` (if :py:`direction is Direction.Bidir`)
|
||||
oe: :py:`unsigned(1, init=1)` (if :py:`direction is Direction.Output`)
|
||||
"""
|
||||
def __init__(self, direction, width):
|
||||
self._direction = Direction(direction)
|
||||
self._width = operator.index(width)
|
||||
members = {}
|
||||
if self._direction is not Direction.Output:
|
||||
members["i"] = wiring.In(self._width)
|
||||
if self._direction is not Direction.Input:
|
||||
members["o"] = wiring.Out(self._width)
|
||||
members["oe"] = wiring.Out(1, init=int(self._direction is Direction.Output))
|
||||
super().__init__(members)
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self._direction
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.direction == other.direction and self.width == other.width
|
||||
|
||||
def __repr__(self):
|
||||
return f"FFBuffer.Signature({self.direction}, {self.width})"
|
||||
|
||||
def __init__(self, direction, port, *, i_domain=None, o_domain=None):
|
||||
if not isinstance(port, PortLike):
|
||||
raise TypeError(f"'port' must be a 'PortLike', not {port!r}")
|
||||
self._port = port
|
||||
super().__init__(FFBuffer.Signature(direction, len(port)).flip())
|
||||
if self.signature.direction is not Direction.Output:
|
||||
self._i_domain = i_domain or "sync"
|
||||
elif i_domain is not None:
|
||||
raise ValueError("Output buffer doesn't have an input domain")
|
||||
if self.signature.direction is not Direction.Input:
|
||||
self._o_domain = o_domain or "sync"
|
||||
elif o_domain is not None:
|
||||
raise ValueError("Input buffer doesn't have an output domain")
|
||||
if port.direction is Direction.Input and self.direction is not Direction.Input:
|
||||
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
|
||||
if port.direction is Direction.Output and self.direction is not Direction.Output:
|
||||
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self.signature.direction
|
||||
|
||||
@property
|
||||
def i_domain(self):
|
||||
if self.direction is Direction.Output:
|
||||
raise AttributeError("Output buffer doesn't have an input domain")
|
||||
return self._i_domain
|
||||
|
||||
@property
|
||||
def o_domain(self):
|
||||
if self.direction is Direction.Input:
|
||||
raise AttributeError("Input buffer doesn't have an output domain")
|
||||
return self._o_domain
|
||||
|
||||
def elaborate(self, platform):
|
||||
if hasattr(platform, "get_io_buffer"):
|
||||
return platform.get_io_buffer(self)
|
||||
|
||||
m = Module()
|
||||
|
||||
m.submodules.io_buffer = io_buffer = Buffer(self.direction, self.port)
|
||||
|
||||
if self.direction is not Direction.Output:
|
||||
i_ff = Signal(len(self.port), reset_less=True)
|
||||
m.d[self.i_domain] += i_ff.eq(io_buffer.i)
|
||||
m.d.comb += self.i.eq(i_ff)
|
||||
|
||||
if self.direction is not Direction.Input:
|
||||
o_ff = Signal(len(self.port), reset_less=True)
|
||||
oe_ff = Signal(reset_less=True)
|
||||
m.d[self.o_domain] += o_ff.eq(self.o)
|
||||
m.d[self.o_domain] += oe_ff.eq(self.oe)
|
||||
m.d.comb += io_buffer.o.eq(o_ff)
|
||||
m.d.comb += io_buffer.oe.eq(oe_ff)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class DDRBuffer(wiring.Component):
|
||||
"""A double data rate registered I/O buffer.
|
||||
|
||||
In the input direction, the port is sampled at both edges of the input clock domain.
|
||||
The data sampled on the active clock edge of the domain appears on :py:`i[0]` with a delay
|
||||
of 1 clock cycle. The data sampled on the opposite clock edge appears on :py:`i[1]` with a delay
|
||||
of 0.5 clock cycle. Both :py:`i[0]` and :py:`i[1]` thus change on the active clock edge of the domain.
|
||||
|
||||
In the output direction, both :py:`o[0]` and :py:`o[1]` are sampled on the active clock edge
|
||||
of the domain. The value of :py:`o[0]` immediately appears on the output port. The value
|
||||
of :py:`o[1]` then appears on the output port on the opposite edge, with a delay of 0.5 clock cycle.
|
||||
|
||||
Support for this compoment is platform-specific, and may be missing on some platforms.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
port : :class:`PortLike`
|
||||
i_domain : :class:`str`
|
||||
Domain for input register. Only used when :py:`direction in (Direction.Input, Direction.Bidir)`.
|
||||
o_domain : :class:`str`
|
||||
Domain for output and output enable registers. Only used when
|
||||
:py:`direction in (Direction.Output, Direction.Bidir)`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
signature : DDRBuffer.Signature
|
||||
Created based on constructor arguments.
|
||||
"""
|
||||
class Signature(wiring.Signature):
|
||||
"""A signature of a double data rate registered I/O buffer.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
direction : :class:`Direction`
|
||||
width : :class:`int`
|
||||
|
||||
Attributes
|
||||
----------
|
||||
i: :py:`unsigned(ArrayLayout(width, 2))` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
|
||||
o: :py:`unsigned(ArrayLayout(width, 2))` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
|
||||
oe: :py:`unsigned(1, init=0)` (if :py:`direction is Direction.Bidir`)
|
||||
oe: :py:`unsigned(1, init=1)` (if :py:`direction is Direction.Output`)
|
||||
"""
|
||||
def __init__(self, direction, width):
|
||||
self._direction = Direction(direction)
|
||||
self._width = operator.index(width)
|
||||
members = {}
|
||||
if self._direction is not Direction.Output:
|
||||
members["i"] = wiring.In(data.ArrayLayout(self._width, 2))
|
||||
if self._direction is not Direction.Input:
|
||||
members["o"] = wiring.Out(data.ArrayLayout(self._width, 2))
|
||||
members["oe"] = wiring.Out(1, init=int(self._direction is Direction.Output))
|
||||
super().__init__(members)
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self._direction
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and self.direction == other.direction and self.width == other.width
|
||||
|
||||
def __repr__(self):
|
||||
return f"DDRBuffer.Signature({self.direction}, {self.width})"
|
||||
|
||||
def __init__(self, direction, port, *, i_domain=None, o_domain=None):
|
||||
if not isinstance(port, PortLike):
|
||||
raise TypeError(f"'port' must be a 'PortLike', not {port!r}")
|
||||
self._port = port
|
||||
super().__init__(DDRBuffer.Signature(direction, len(port)).flip())
|
||||
if self.signature.direction is not Direction.Output:
|
||||
self._i_domain = i_domain or "sync"
|
||||
elif i_domain is not None:
|
||||
raise ValueError("Output buffer doesn't have an input domain")
|
||||
if self.signature.direction is not Direction.Input:
|
||||
self._o_domain = o_domain or "sync"
|
||||
elif o_domain is not None:
|
||||
raise ValueError("Input buffer doesn't have an output domain")
|
||||
if port.direction is Direction.Input and self.direction is not Direction.Input:
|
||||
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
|
||||
if port.direction is Direction.Output and self.direction is not Direction.Output:
|
||||
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
return self._port
|
||||
|
||||
@property
|
||||
def direction(self):
|
||||
return self.signature.direction
|
||||
|
||||
@property
|
||||
def i_domain(self):
|
||||
if self.direction is Direction.Output:
|
||||
raise AttributeError("Output buffer doesn't have an input domain")
|
||||
return self._i_domain
|
||||
|
||||
@property
|
||||
def o_domain(self):
|
||||
if self.direction is Direction.Input:
|
||||
raise AttributeError("Input buffer doesn't have an output domain")
|
||||
return self._o_domain
|
||||
|
||||
def elaborate(self, platform):
|
||||
if hasattr(platform, "get_io_buffer"):
|
||||
return platform.get_io_buffer(self)
|
||||
|
||||
raise NotImplementedError("DDR buffers cannot be elaborated without a supported platform")
|
||||
|
||||
|
||||
class Pin(wiring.PureInterface):
|
||||
"""
|
||||
An interface to an I/O buffer or a group of them that provides uniform access to input, output,
|
||||
|
|
|
@ -102,6 +102,7 @@ Standard library changes
|
|||
* Added: :class:`amaranth.lib.data.Const` class. (`RFC 51`_)
|
||||
* Changed: :meth:`amaranth.lib.data.Layout.const` returns a :class:`amaranth.lib.data.Const`, not a view (`RFC 51`_)
|
||||
* Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
|
||||
* Added: :class:`amaranth.lib.io.Buffer`, :class:`amaranth.lib.io.FFBuffer`, :class:`amaranth.lib.io.DDRBuffer`. (`RFC 55`_)
|
||||
* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
|
||||
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
|
||||
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# amaranth: UnusedElaboratable=no
|
||||
|
||||
from amaranth.hdl import *
|
||||
from amaranth.sim import *
|
||||
from amaranth.hdl._ir import build_netlist
|
||||
from amaranth.lib.io import *
|
||||
from amaranth.lib.wiring import *
|
||||
from amaranth.lib import wiring, data
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
@ -169,6 +173,548 @@ class DifferentialPortTestCase(FHDLTestCase):
|
|||
self.assertRepr(iport, "DifferentialPort((io-port iop), (io-port ion), invert=(False, True, False, True), direction=Direction.Output)")
|
||||
|
||||
|
||||
class BufferTestCase(FHDLTestCase):
|
||||
def test_signature(self):
|
||||
sig_i = Buffer.Signature("i", 4)
|
||||
self.assertEqual(sig_i.direction, Direction.Input)
|
||||
self.assertEqual(sig_i.width, 4)
|
||||
self.assertEqual(sig_i.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(4),
|
||||
}))
|
||||
sig_o = Buffer.Signature("o", 4)
|
||||
self.assertEqual(sig_o.direction, Direction.Output)
|
||||
self.assertEqual(sig_o.width, 4)
|
||||
self.assertEqual(sig_o.members, wiring.SignatureMembers({
|
||||
"o": wiring.Out(4),
|
||||
"oe": wiring.Out(1, init=1),
|
||||
}))
|
||||
sig_io = Buffer.Signature("io", 4)
|
||||
self.assertEqual(sig_io.direction, Direction.Bidir)
|
||||
self.assertEqual(sig_io.width, 4)
|
||||
self.assertEqual(sig_io.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(4),
|
||||
"o": wiring.Out(4),
|
||||
"oe": wiring.Out(1, init=0),
|
||||
}))
|
||||
self.assertNotEqual(sig_i, sig_io)
|
||||
self.assertEqual(sig_i, sig_i)
|
||||
self.assertRepr(sig_io, "Buffer.Signature(Direction.Bidir, 4)")
|
||||
|
||||
def test_construct(self):
|
||||
io = IOPort(4)
|
||||
port = SingleEndedPort(io)
|
||||
buf = Buffer("i", port)
|
||||
self.assertEqual(buf.direction, Direction.Input)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "Buffer.Signature(Direction.Input, 4).flip()")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
io = IOPort(4)
|
||||
port_i = SingleEndedPort(io, direction="i")
|
||||
port_o = SingleEndedPort(io, direction="o")
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'port' must be a 'PortLike', not \(io-port io\)$"):
|
||||
Buffer("io", io)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Input port cannot be used with Bidir buffer$"):
|
||||
Buffer("io", port_i)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Output port cannot be used with Input buffer$"):
|
||||
Buffer("i", port_o)
|
||||
|
||||
def test_elaborate(self):
|
||||
io = IOPort(4)
|
||||
|
||||
port = SingleEndedPort(io)
|
||||
buf = Buffer("io", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i, buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(output 'i' 1.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(output 'i' 1.0:4)
|
||||
))
|
||||
(cell 1 0 (iob inout 0.0:4 0.2:6 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
port = SingleEndedPort(io, invert=[False, True, False, True])
|
||||
buf = Buffer("io", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i, buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(output 'i' 2.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(output 'i' 2.0:4)
|
||||
))
|
||||
(cell 1 0 (^ 0.2:6 4'd10))
|
||||
(cell 2 0 (^ 3.0:4 4'd10))
|
||||
(cell 3 0 (iob inout 0.0:4 1.0:4 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = Buffer("i", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(output 'i' 1.0:4)
|
||||
(io input 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(output 'i' 1.0:4)
|
||||
))
|
||||
(cell 1 0 (^ 2.0:4 4'd10))
|
||||
(cell 2 0 (iob input 0.0:4))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = Buffer("o", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(io output 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
))
|
||||
(cell 1 0 (^ 0.2:6 4'd10))
|
||||
(cell 2 0 (iob output 0.0:4 1.0:4 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
def test_elaborate_diff(self):
|
||||
iop = IOPort(4)
|
||||
ion = IOPort(4)
|
||||
|
||||
port = DifferentialPort(iop, ion)
|
||||
buf = Buffer("io", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i, buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(output 'i' 1.0:4)
|
||||
(io inout 'iop' 0.0:4)
|
||||
(io output 'ion' 1.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(output 'i' 1.0:4)
|
||||
))
|
||||
(cell 1 0 (iob inout 0.0:4 0.2:6 0.6))
|
||||
(cell 2 0 (~ 0.2:6))
|
||||
(cell 3 0 (iob output 1.0:4 2.0:4 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
port = DifferentialPort(iop, ion, invert=[False, True, False, True])
|
||||
buf = Buffer("io", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i, buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(output 'i' 2.0:4)
|
||||
(io inout 'iop' 0.0:4)
|
||||
(io output 'ion' 1.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(output 'i' 2.0:4)
|
||||
))
|
||||
(cell 1 0 (^ 0.2:6 4'd10))
|
||||
(cell 2 0 (^ 3.0:4 4'd10))
|
||||
(cell 3 0 (iob inout 0.0:4 1.0:4 0.6))
|
||||
(cell 4 0 (~ 1.0:4))
|
||||
(cell 5 0 (iob output 1.0:4 4.0:4 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = Buffer("i", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(output 'i' 1.0:4)
|
||||
(io input 'iop' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(output 'i' 1.0:4)
|
||||
))
|
||||
(cell 1 0 (^ 2.0:4 4'd10))
|
||||
(cell 2 0 (iob input 0.0:4))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = Buffer("o", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(io output 'iop' 0.0:4)
|
||||
(io output 'ion' 1.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
))
|
||||
(cell 1 0 (^ 0.2:6 4'd10))
|
||||
(cell 2 0 (iob output 0.0:4 1.0:4 0.6))
|
||||
(cell 3 0 (~ 1.0:4))
|
||||
(cell 4 0 (iob output 1.0:4 3.0:4 0.6))
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
class FFBufferTestCase(FHDLTestCase):
|
||||
def test_signature(self):
|
||||
sig_i = FFBuffer.Signature("i", 4)
|
||||
self.assertEqual(sig_i.direction, Direction.Input)
|
||||
self.assertEqual(sig_i.width, 4)
|
||||
self.assertEqual(sig_i.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(4),
|
||||
}))
|
||||
sig_o = FFBuffer.Signature("o", 4)
|
||||
self.assertEqual(sig_o.direction, Direction.Output)
|
||||
self.assertEqual(sig_o.width, 4)
|
||||
self.assertEqual(sig_o.members, wiring.SignatureMembers({
|
||||
"o": wiring.Out(4),
|
||||
"oe": wiring.Out(1, init=1),
|
||||
}))
|
||||
sig_io = FFBuffer.Signature("io", 4)
|
||||
self.assertEqual(sig_io.direction, Direction.Bidir)
|
||||
self.assertEqual(sig_io.width, 4)
|
||||
self.assertEqual(sig_io.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(4),
|
||||
"o": wiring.Out(4),
|
||||
"oe": wiring.Out(1, init=0),
|
||||
}))
|
||||
self.assertNotEqual(sig_i, sig_io)
|
||||
self.assertEqual(sig_i, sig_i)
|
||||
self.assertRepr(sig_io, "FFBuffer.Signature(Direction.Bidir, 4)")
|
||||
|
||||
def test_construct(self):
|
||||
io = IOPort(4)
|
||||
port = SingleEndedPort(io)
|
||||
buf = FFBuffer("i", port)
|
||||
self.assertEqual(buf.direction, Direction.Input)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Input, 4).flip()")
|
||||
self.assertEqual(buf.i_domain, "sync")
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^Input buffer doesn't have an output domain$"):
|
||||
buf.o_domain
|
||||
buf = FFBuffer("i", port, i_domain="inp")
|
||||
self.assertEqual(buf.i_domain, "inp")
|
||||
buf = FFBuffer("o", port)
|
||||
self.assertEqual(buf.direction, Direction.Output)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Output, 4).flip()")
|
||||
self.assertEqual(buf.o_domain, "sync")
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^Output buffer doesn't have an input domain$"):
|
||||
buf.i_domain
|
||||
buf = FFBuffer("o", port, o_domain="out")
|
||||
self.assertEqual(buf.o_domain, "out")
|
||||
buf = FFBuffer("io", port)
|
||||
self.assertEqual(buf.direction, Direction.Bidir)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Bidir, 4).flip()")
|
||||
self.assertEqual(buf.i_domain, "sync")
|
||||
self.assertEqual(buf.o_domain, "sync")
|
||||
buf = FFBuffer("io", port, i_domain="input", o_domain="output")
|
||||
self.assertEqual(buf.i_domain, "input")
|
||||
self.assertEqual(buf.o_domain, "output")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
io = IOPort(4)
|
||||
port = SingleEndedPort(io)
|
||||
port_i = SingleEndedPort(io, direction="i")
|
||||
port_o = SingleEndedPort(io, direction="o")
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'port' must be a 'PortLike', not \(io-port io\)$"):
|
||||
FFBuffer("io", io)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Input port cannot be used with Bidir buffer$"):
|
||||
FFBuffer("io", port_i)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Output port cannot be used with Input buffer$"):
|
||||
FFBuffer("i", port_o)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Input buffer doesn't have an output domain$"):
|
||||
FFBuffer("i", port, o_domain="output")
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Output buffer doesn't have an input domain$"):
|
||||
FFBuffer("o", port, i_domain="input")
|
||||
|
||||
def test_elaborate(self):
|
||||
io = IOPort(4)
|
||||
|
||||
port = SingleEndedPort(io)
|
||||
m = Module()
|
||||
m.domains.inp = ClockDomain()
|
||||
m.domains.outp = ClockDomain()
|
||||
m.submodules.buf = buf = FFBuffer("io", port, i_domain="inp", o_domain="outp")
|
||||
nl = build_netlist(Fragment.get(m, None), [
|
||||
buf.i, buf.o, buf.oe,
|
||||
ClockSignal("inp"), ResetSignal("inp"),
|
||||
ClockSignal("outp"), ResetSignal("outp"),
|
||||
])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(input 'inp_clk' 0.7)
|
||||
(input 'inp_rst' 0.8)
|
||||
(input 'outp_clk' 0.9)
|
||||
(input 'outp_rst' 0.10)
|
||||
(output 'i' 2.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(module 1 0 ('top' 'buf')
|
||||
(input 'o$11' 0.2:6)
|
||||
(input 'oe$12' 0.6)
|
||||
(input 'inp_clk' 0.7)
|
||||
(input 'inp_rst' 0.8)
|
||||
(input 'outp_clk' 0.9)
|
||||
(input 'outp_rst' 0.10)
|
||||
(output 'i_ff' 2.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(module 2 1 ('top' 'buf' 'io_buffer')
|
||||
(output 'i' 1.0:4)
|
||||
(input 'o' 3.0:4)
|
||||
(input 'oe' 4.0)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(input 'inp_clk' 7:8)
|
||||
(input 'inp_rst' 8:9)
|
||||
(input 'outp_clk' 9:10)
|
||||
(input 'outp_rst' 10:11)
|
||||
(output 'i' 2.0:4)
|
||||
))
|
||||
(cell 1 2 (iob inout 0.0:4 3.0:4 4.0))
|
||||
(cell 2 1 (flipflop 1.0:4 0 pos 0.7 0))
|
||||
(cell 3 1 (flipflop 0.2:6 0 pos 0.9 0))
|
||||
(cell 4 1 (flipflop 0.6 0 pos 0.9 0))
|
||||
)
|
||||
""")
|
||||
|
||||
port = SingleEndedPort(io, invert=[False, True, False, True])
|
||||
m = Module()
|
||||
m.domains.inp = ClockDomain(reset_less=True)
|
||||
m.domains.outp = ClockDomain(reset_less=True)
|
||||
m.submodules.buf = buf = FFBuffer("io", port, i_domain="inp", o_domain="outp")
|
||||
nl = build_netlist(Fragment.get(m, None), [
|
||||
buf.i, buf.o, buf.oe,
|
||||
ClockSignal("inp"), ClockSignal("outp"),
|
||||
])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(input 'inp_clk' 0.7)
|
||||
(input 'outp_clk' 0.8)
|
||||
(output 'i' 4.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(module 1 0 ('top' 'buf')
|
||||
(input 'o$9' 0.2:6)
|
||||
(input 'oe$10' 0.6)
|
||||
(input 'inp_clk' 0.7)
|
||||
(input 'outp_clk' 0.8)
|
||||
(output 'i_ff' 4.0:4)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(module 2 1 ('top' 'buf' 'io_buffer')
|
||||
(output 'i' 2.0:4)
|
||||
(input 'o' 5.0:4)
|
||||
(input 'oe' 6.0)
|
||||
(io inout 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(input 'inp_clk' 7:8)
|
||||
(input 'outp_clk' 8:9)
|
||||
(output 'i' 4.0:4)
|
||||
))
|
||||
(cell 1 2 (^ 5.0:4 4'd10))
|
||||
(cell 2 2 (^ 3.0:4 4'd10))
|
||||
(cell 3 2 (iob inout 0.0:4 1.0:4 6.0))
|
||||
(cell 4 1 (flipflop 2.0:4 0 pos 0.7 0))
|
||||
(cell 5 1 (flipflop 0.2:6 0 pos 0.8 0))
|
||||
(cell 6 1 (flipflop 0.6 0 pos 0.8 0))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = FFBuffer("i", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.i])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'clk' 0.2)
|
||||
(input 'rst' 0.3)
|
||||
(output 'i' 3.0:4)
|
||||
(io input 'io' 0.0:4)
|
||||
)
|
||||
(module 1 0 ('top' 'io_buffer')
|
||||
(output 'i' 1.0:4)
|
||||
(io input 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'clk' 2:3)
|
||||
(input 'rst' 3:4)
|
||||
(output 'i' 3.0:4)
|
||||
))
|
||||
(cell 1 1 (^ 2.0:4 4'd10))
|
||||
(cell 2 1 (iob input 0.0:4))
|
||||
(cell 3 0 (flipflop 1.0:4 0 pos 0.2 0))
|
||||
)
|
||||
""")
|
||||
|
||||
buf = FFBuffer("o", port)
|
||||
nl = build_netlist(Fragment.get(buf, None), [buf.o, buf.oe])
|
||||
self.assertRepr(nl, """
|
||||
(
|
||||
(module 0 None ('top')
|
||||
(input 'o' 0.2:6)
|
||||
(input 'oe' 0.6)
|
||||
(input 'clk' 0.7)
|
||||
(input 'rst' 0.8)
|
||||
(io output 'io' 0.0:4)
|
||||
)
|
||||
(module 1 0 ('top' 'io_buffer')
|
||||
(input 'o' 3.0:4)
|
||||
(input 'oe' 4.0)
|
||||
(io output 'io' 0.0:4)
|
||||
)
|
||||
(cell 0 0 (top
|
||||
(input 'o' 2:6)
|
||||
(input 'oe' 6:7)
|
||||
(input 'clk' 7:8)
|
||||
(input 'rst' 8:9)
|
||||
))
|
||||
(cell 1 1 (^ 3.0:4 4'd10))
|
||||
(cell 2 1 (iob output 0.0:4 1.0:4 4.0))
|
||||
(cell 3 0 (flipflop 0.2:6 0 pos 0.7 0))
|
||||
(cell 4 0 (flipflop 0.6 0 pos 0.7 0))
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
class DDRBufferTestCase(FHDLTestCase):
|
||||
def test_signature(self):
|
||||
sig_i = DDRBuffer.Signature("i", 4)
|
||||
self.assertEqual(sig_i.direction, Direction.Input)
|
||||
self.assertEqual(sig_i.width, 4)
|
||||
self.assertEqual(sig_i.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(data.ArrayLayout(4, 2)),
|
||||
}))
|
||||
sig_o = DDRBuffer.Signature("o", 4)
|
||||
self.assertEqual(sig_o.direction, Direction.Output)
|
||||
self.assertEqual(sig_o.width, 4)
|
||||
self.assertEqual(sig_o.members, wiring.SignatureMembers({
|
||||
"o": wiring.Out(data.ArrayLayout(4, 2)),
|
||||
"oe": wiring.Out(1, init=1),
|
||||
}))
|
||||
sig_io = DDRBuffer.Signature("io", 4)
|
||||
self.assertEqual(sig_io.direction, Direction.Bidir)
|
||||
self.assertEqual(sig_io.width, 4)
|
||||
self.assertEqual(sig_io.members, wiring.SignatureMembers({
|
||||
"i": wiring.In(data.ArrayLayout(4, 2)),
|
||||
"o": wiring.Out(data.ArrayLayout(4, 2)),
|
||||
"oe": wiring.Out(1, init=0),
|
||||
}))
|
||||
self.assertNotEqual(sig_i, sig_io)
|
||||
self.assertEqual(sig_i, sig_i)
|
||||
self.assertRepr(sig_io, "DDRBuffer.Signature(Direction.Bidir, 4)")
|
||||
|
||||
def test_construct(self):
|
||||
io = IOPort(4)
|
||||
port = SingleEndedPort(io)
|
||||
buf = DDRBuffer("i", port)
|
||||
self.assertEqual(buf.direction, Direction.Input)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Input, 4).flip()")
|
||||
self.assertEqual(buf.i_domain, "sync")
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^Input buffer doesn't have an output domain$"):
|
||||
buf.o_domain
|
||||
buf = DDRBuffer("i", port, i_domain="inp")
|
||||
self.assertEqual(buf.i_domain, "inp")
|
||||
buf = DDRBuffer("o", port)
|
||||
self.assertEqual(buf.direction, Direction.Output)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Output, 4).flip()")
|
||||
self.assertEqual(buf.o_domain, "sync")
|
||||
with self.assertRaisesRegex(AttributeError,
|
||||
r"^Output buffer doesn't have an input domain$"):
|
||||
buf.i_domain
|
||||
buf = DDRBuffer("o", port, o_domain="out")
|
||||
self.assertEqual(buf.o_domain, "out")
|
||||
buf = DDRBuffer("io", port)
|
||||
self.assertEqual(buf.direction, Direction.Bidir)
|
||||
self.assertIs(buf.port, port)
|
||||
self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Bidir, 4).flip()")
|
||||
self.assertEqual(buf.i_domain, "sync")
|
||||
self.assertEqual(buf.o_domain, "sync")
|
||||
buf = DDRBuffer("io", port, i_domain="input", o_domain="output")
|
||||
self.assertEqual(buf.i_domain, "input")
|
||||
self.assertEqual(buf.o_domain, "output")
|
||||
|
||||
def test_construct_wrong(self):
|
||||
io = IOPort(4)
|
||||
port = SingleEndedPort(io)
|
||||
port_i = SingleEndedPort(io, direction="i")
|
||||
port_o = SingleEndedPort(io, direction="o")
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^'port' must be a 'PortLike', not \(io-port io\)$"):
|
||||
DDRBuffer("io", io)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Input port cannot be used with Bidir buffer$"):
|
||||
DDRBuffer("io", port_i)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Output port cannot be used with Input buffer$"):
|
||||
DDRBuffer("i", port_o)
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Input buffer doesn't have an output domain$"):
|
||||
DDRBuffer("i", port, o_domain="output")
|
||||
with self.assertRaisesRegex(ValueError,
|
||||
r"^Output buffer doesn't have an input domain$"):
|
||||
DDRBuffer("o", port, i_domain="input")
|
||||
|
||||
|
||||
class PinSignatureTestCase(FHDLTestCase):
|
||||
def assertSignatureEqual(self, signature, expected):
|
||||
self.assertEqual(signature.members, Signature(expected).members)
|
||||
|
|
Loading…
Reference in a new issue