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 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,

View file

@ -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`_)

View file

@ -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)