lib.io: Implement *Buffer from RFC 55.

This commit is contained in:
Wanda 2024-03-19 03:42:21 +01:00 committed by Catherine
parent 81eae1dd35
commit 456dcaeb7b
3 changed files with 965 additions and 25 deletions

View file

@ -1,13 +1,19 @@
import enum import enum
import operator
from abc import ABCMeta, abstractmethod
from collections.abc import Iterable from collections.abc import Iterable
from ..hdl import * from ..hdl import *
from ..lib import wiring from ..lib import wiring, data
from ..lib.wiring import In, Out from ..lib.wiring import In, Out
from .. import tracer from .. import tracer
__all__ = ["Direction", "SingleEndedPort", "DifferentialPort", "Pin"] __all__ = [
"Direction", "PortLike", "SingleEndedPort", "DifferentialPort",
"Buffer", "FFBuffer", "DDRBuffer",
"Pin",
]
class Direction(enum.Enum): class Direction(enum.Enum):
@ -41,16 +47,52 @@ class Direction(enum.Enum):
raise ValueError("Cannot combine input port with output port") 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. """Represents a single-ended I/O port with optional inversion.
Parameters Parameters
---------- ----------
io : IOValue io : :class:`IOValue`
The raw I/O value being wrapped. 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 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: This can be used for various purposes:
@ -59,9 +101,9 @@ class SingleEndedPort:
on the pin on the pin
If the value is a simple :class:`bool`, it is used for all bits of this port. If the value 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. 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 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. :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. If equal to :attr:`Direction.Bidir`, this port can be used with buffers of any direction.
@ -84,18 +126,18 @@ class SingleEndedPort:
@property @property
def io(self): def io(self):
"""The ``io`` argument passed to the constructor.""" """The :py:`io` argument passed to the constructor."""
return self._io return self._io
@property @property
def invert(self): 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`.""" of :class:`bool`."""
return self._invert return self._invert
@property @property
def direction(self): 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 return self._direction
def __len__(self): def __len__(self):
@ -103,7 +145,7 @@ class SingleEndedPort:
return len(self._io) return len(self._io)
def __invert__(self): 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), return SingleEndedPort(self._io, invert=tuple(not inv for inv in self._invert),
direction=self._direction) direction=self._direction)
@ -141,19 +183,19 @@ class SingleEndedPort:
return f"SingleEndedPort({self._io!r}, invert={invert!r}, direction={self._direction})" 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. """Represents a differential I/O port with optional inversion.
Parameters Parameters
---------- ----------
p : IOValue p : :class:`IOValue`
The raw I/O value used as positive (true) half of the port. 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 The raw I/O value used as negative (complemented) half of the port. Must have the same
length as ``p``. length as :py:`p`.
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 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: 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) - 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 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. 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 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. :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. If equal to :attr:`Direction.Bidir`, this port can be used with buffers of any direction.
@ -190,23 +232,23 @@ class DifferentialPort:
@property @property
def p(self): def p(self):
"""The ``p`` argument passed to the constructor.""" """The :py:`p` argument passed to the constructor."""
return self._p return self._p
@property @property
def n(self): def n(self):
"""The ``n`` argument passed to the constructor.""" """The :py:`n` argument passed to the constructor."""
return self._n return self._n
@property @property
def invert(self): 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`.""" of :class:`bool`."""
return self._invert return self._invert
@property @property
def direction(self): 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 return self._direction
def __len__(self): def __len__(self):
@ -214,7 +256,7 @@ class DifferentialPort:
return len(self._p) return len(self._p)
def __invert__(self): 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), return DifferentialPort(self._p, self._n, invert=tuple(not inv for inv in self._invert),
direction=self._direction) direction=self._direction)
@ -253,6 +295,357 @@ class DifferentialPort:
return f"DifferentialPort({self._p!r}, {self._n!r}, invert={invert!r}, direction={self._direction})" 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): class Pin(wiring.PureInterface):
""" """
An interface to an I/O buffer or a group of them that provides uniform access to input, output, An interface to an I/O buffer or a group of them that provides uniform access to input, output,

View file

@ -102,6 +102,7 @@ Standard library changes
* Added: :class:`amaranth.lib.data.Const` class. (`RFC 51`_) * 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`_) * 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.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) :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.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_) * Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)

View file

@ -1,7 +1,11 @@
# amaranth: UnusedElaboratable=no
from amaranth.hdl import * from amaranth.hdl import *
from amaranth.sim import * from amaranth.sim import *
from amaranth.hdl._ir import build_netlist
from amaranth.lib.io import * from amaranth.lib.io import *
from amaranth.lib.wiring import * from amaranth.lib.wiring import *
from amaranth.lib import wiring, data
from .utils import * 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)") 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): class PinSignatureTestCase(FHDLTestCase):
def assertSignatureEqual(self, signature, expected): def assertSignatureEqual(self, signature, expected):
self.assertEqual(signature.members, Signature(expected).members) self.assertEqual(signature.members, Signature(expected).members)