Skip to content

Commit de0846e

Browse files
committed
lib.io: Implement *Buffer from RFC 55.
1 parent 598cf8d commit de0846e

File tree

3 files changed

+582
-2
lines changed

3 files changed

+582
-2
lines changed

amaranth/lib/io.py

Lines changed: 363 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import enum
2+
import operator
23
from collections.abc import Iterable
34

45
from ..hdl import *
5-
from ..lib import wiring
6+
from ..lib import wiring, data
67
from ..lib.wiring import In, Out
78
from .. import tracer
89

910

10-
__all__ = ["Direction", "SingleEndedPort", "DifferentialPort", "Pin"]
11+
__all__ = [
12+
"Direction", "SingleEndedPort", "DifferentialPort",
13+
"Buffer", "FFBuffer", "DDRBuffer",
14+
"Pin",
15+
]
1116

1217

1318
class Direction(enum.Enum):
@@ -253,6 +258,362 @@ def __repr__(self):
253258
return f"DifferentialPort({self._p!r}, {self._n!r}, invert={invert!r}, direction={self._direction})"
254259

255260

261+
class Buffer(wiring.Component):
262+
"""A combinatorial I/O buffer.
263+
264+
Parameters
265+
----------
266+
direction : Direction
267+
port : SingleEndedPort, DifferentialPort, or a custom platform-specific type
268+
269+
Attributes
270+
----------
271+
signature : Buffer.Signature
272+
Created based on constructor arguments.
273+
"""
274+
class Signature(wiring.Signature):
275+
"""A signature of a combinatorial I/O buffer.
276+
277+
Parameters
278+
----------
279+
direction : Direction
280+
width : int
281+
282+
Attributes
283+
----------
284+
i: :py:`unsigned(width)` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
285+
o: :py:`unsigned(width)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
286+
oe: :py:`unsigned(1)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
287+
"""
288+
def __init__(self, direction, width):
289+
self._direction = Direction(direction)
290+
self._width = operator.index(width)
291+
members = {}
292+
if self._direction is not Direction.Output:
293+
members["i"] = wiring.Out(self._width)
294+
if self._direction is not Direction.Input:
295+
members["o"] = wiring.In(self._width)
296+
members["oe"] = wiring.In(1, init=int(self._direction is Direction.Output))
297+
super().__init__(members)
298+
299+
@property
300+
def direction(self):
301+
return self._direction
302+
303+
@property
304+
def width(self):
305+
return self._width
306+
307+
def __eq__(self, other):
308+
return type(self) == type(other) and self.direction == other.direction and self.width == other.width
309+
310+
def __repr__(self):
311+
return f"Buffer.Signature({self.direction}, {self.width})"
312+
313+
def __init__(self, direction, port):
314+
if not (hasattr(port, "direction") and isinstance(port.direction, Direction)):
315+
raise TypeError(f"'port' must be a 'SingleEndedPort', 'DifferentialPort', "
316+
f"or a platform-specific object, not {port!r}")
317+
self._port = port
318+
super().__init__(Buffer.Signature(direction, len(port)))
319+
if port.direction is Direction.Input and self.direction is not Direction.Input:
320+
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
321+
if port.direction is Direction.Output and self.direction is not Direction.Output:
322+
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
323+
324+
@property
325+
def port(self):
326+
return self._port
327+
328+
@property
329+
def direction(self):
330+
return self.signature.direction
331+
332+
def elaborate(self, platform):
333+
if hasattr(platform, "get_io_buffer"):
334+
res = platform.get_io_buffer(self)
335+
if res is not None:
336+
return res
337+
338+
m = Module()
339+
340+
invert = sum(bit << idx for idx, bit in enumerate(self._port.invert))
341+
if self.direction is not Direction.Input:
342+
if invert:
343+
o_inv = Signal.like(self.o)
344+
m.d.comb += o_inv.eq(self.o ^ invert)
345+
else:
346+
o_inv = self.o
347+
if self.direction is not Direction.Output:
348+
if invert:
349+
i_inv = Signal.like(self.i)
350+
m.d.comb += self.i.eq(i_inv ^ invert)
351+
else:
352+
i_inv = self.i
353+
354+
if isinstance(self._port, SingleEndedPort):
355+
if self.direction is Direction.Input:
356+
m.submodules.iob = IOBufferInstance(self._port.io, i=i_inv)
357+
elif self.direction is Direction.Output:
358+
m.submodules.iob = IOBufferInstance(self._port.io, o=o_inv, oe=self.oe)
359+
else:
360+
m.submodules.iob = IOBufferInstance(self._port.io, o=o_inv, oe=self.oe, i=i_inv)
361+
elif isinstance(self._port, DifferentialPort):
362+
if self.direction is Direction.Input:
363+
m.submodules.iob_p = IOBufferInstance(self._port.p, i=i_inv)
364+
elif self.direction is Direction.Output:
365+
m.submodules.iob_p = IOBufferInstance(self._port.p, o=o_inv, oe=self.oe)
366+
m.submodules.iob_n = IOBufferInstance(self._port.n, o=~o_inv, oe=self.oe)
367+
else:
368+
m.submodules.iob_p = IOBufferInstance(self._port.p, o=o_inv, oe=self.oe, i=i_inv)
369+
m.submodules.iob_n = IOBufferInstance(self._port.n, o=~o_inv, oe=self.oe)
370+
else:
371+
raise TypeError("Cannot elaborate generic 'Buffer' with port {self._port!r}")
372+
373+
return m
374+
375+
376+
class FFBuffer(wiring.Component):
377+
"""A registered I/O buffer.
378+
379+
Equivalent to a plain :class:`Buffer` combined with reset-less registers on ``i``, ``o``, ``oe``.
380+
381+
Parameters
382+
----------
383+
direction : Direction
384+
port : SingleEndedPort, DifferentialPort, or a custom platform-specific type
385+
i_domain : str
386+
Domain for input register. Only used when :py:`direction in (Direction.Input, Direction.Bidir)`.
387+
Defaults to :py:`"sync"`
388+
o_domain : str
389+
Domain for output and output enable registers. Only used when
390+
:py:`direction in (Direction.Output, Direction.Bidir)`. Defaults to :py:`"sync"`
391+
392+
Attributes
393+
----------
394+
signature : FFBuffer.Signature
395+
Created based on constructor arguments.
396+
"""
397+
class Signature(wiring.Signature):
398+
"""A signature of a registered I/O buffer.
399+
400+
Parameters
401+
----------
402+
direction : Direction
403+
width : int
404+
405+
Attributes
406+
----------
407+
i: :py:`unsigned(width)` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
408+
o: :py:`unsigned(width)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
409+
oe: :py:`unsigned(1)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
410+
"""
411+
def __init__(self, direction, width):
412+
self._direction = Direction(direction)
413+
self._width = operator.index(width)
414+
members = {}
415+
if self._direction is not Direction.Output:
416+
members["i"] = wiring.Out(self._width)
417+
if self._direction is not Direction.Input:
418+
members["o"] = wiring.In(self._width)
419+
members["oe"] = wiring.In(1, init=int(self._direction is Direction.Output))
420+
super().__init__(members)
421+
422+
@property
423+
def direction(self):
424+
return self._direction
425+
426+
@property
427+
def width(self):
428+
return self._width
429+
430+
def __eq__(self, other):
431+
return type(self) == type(other) and self.direction == other.direction and self.width == other.width
432+
433+
def __repr__(self):
434+
return f"FFBuffer.Signature({self.direction}, {self.width})"
435+
436+
def __init__(self, direction, port, *, i_domain=None, o_domain=None):
437+
if not (hasattr(port, "direction") and isinstance(port.direction, Direction)):
438+
raise TypeError(f"'port' must be a 'SingleEndedPort', 'DifferentialPort', "
439+
f"or a platform-specific object, not {port!r}")
440+
self._port = port
441+
super().__init__(FFBuffer.Signature(direction, len(port)))
442+
if self.signature.direction is not Direction.Output:
443+
self._i_domain = i_domain or "sync"
444+
elif i_domain is not None:
445+
raise ValueError("Output buffer doesn't have an input domain")
446+
if self.signature.direction is not Direction.Input:
447+
self._o_domain = o_domain or "sync"
448+
elif o_domain is not None:
449+
raise ValueError("Input buffer doesn't have an output domain")
450+
if port.direction is Direction.Input and self.direction is not Direction.Input:
451+
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
452+
if port.direction is Direction.Output and self.direction is not Direction.Output:
453+
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
454+
455+
@property
456+
def port(self):
457+
return self._port
458+
459+
@property
460+
def direction(self):
461+
return self.signature.direction
462+
463+
@property
464+
def i_domain(self):
465+
if self.direction is Direction.Output:
466+
raise AttributeError("Output buffer doesn't have an input domain")
467+
return self._i_domain
468+
469+
@property
470+
def o_domain(self):
471+
if self.direction is Direction.Input:
472+
raise AttributeError("Input buffer doesn't have an output domain")
473+
return self._o_domain
474+
475+
def elaborate(self, platform):
476+
if hasattr(platform, "get_io_buffer"):
477+
res = platform.get_io_buffer(self)
478+
if res is not None:
479+
return res
480+
481+
m = Module()
482+
483+
m.submodules.iob = iob = Buffer(self.direction, self.port)
484+
485+
if self.direction is not Direction.Output:
486+
i_tmp = Signal.like(self.i, reset_less=True)
487+
m.d[self.i_domain] += i_tmp.eq(iob.i)
488+
m.d.comb += self.i.eq(i_tmp)
489+
490+
if self.direction is not Direction.Input:
491+
o_tmp = Signal.like(self.o, reset_less=True)
492+
oe_tmp = Signal.like(self.oe, reset_less=True)
493+
m.d[self.o_domain] += o_tmp.eq(self.o)
494+
m.d[self.o_domain] += oe_tmp.eq(self.oe)
495+
m.d.comb += iob.o.eq(o_tmp)
496+
m.d.comb += iob.oe.eq(oe_tmp)
497+
498+
return m
499+
500+
501+
class DDRBuffer(wiring.Component):
502+
"""A double data rate registered I/O buffer.
503+
504+
In the input direction, the port is sampled at both edges of the input clock domain.
505+
The data sampled on the active clock edge of the domain appears on ``i[0]`` with a delay
506+
of 1 clock cycle. The data sampled on the opposite clock edge appears on ``i[1]`` with a delay
507+
of 0.5 clock cycle. Both ``i[0]`` and ``i[1]`` thus change on the active clock edge of the domain.
508+
509+
In the output direction, both ``o[0]`` and ``o[1]`` are sampled on the active clock edge
510+
of the domain. The value of ``o[0]`` immediately appears on the output port. The value
511+
of ``o[1]`` then appears on the output port on the opposite edge, with a delay of 0.5 clock cycle.
512+
513+
The degree of support for this component varies by platform.
514+
515+
Parameters
516+
----------
517+
direction : Direction
518+
port : SingleEndedPort, DifferentialPort, or a custom platform-specific type
519+
i_domain : str
520+
Domain for input register. Only used when :py:`direction in (Direction.Input, Direction.Bidir)`.
521+
o_domain : str
522+
Domain for output and output enable registers. Only used when
523+
:py:`direction in (Direction.Output, Direction.Bidir)`.
524+
525+
Attributes
526+
----------
527+
signature : DDRBuffer.Signature
528+
Created based on constructor arguments.
529+
"""
530+
class Signature(wiring.Signature):
531+
"""A signature of a double data rate registered I/O buffer.
532+
533+
Parameters
534+
----------
535+
direction : Direction
536+
width : int
537+
538+
Attributes
539+
----------
540+
i: :py:`unsigned(ArrayLayout(width, 2))` (if :py:`direction in (Direction.Input, Direction.Bidir)`)
541+
o: :py:`unsigned(ArrayLayout(width, 2))` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
542+
oe: :py:`unsigned(1)` (if :py:`direction in (Direction.Output, Direction.Bidir)`)
543+
"""
544+
def __init__(self, direction, width):
545+
self._direction = Direction(direction)
546+
self._width = operator.index(width)
547+
members = {}
548+
if self._direction is not Direction.Output:
549+
members["i"] = wiring.Out(data.ArrayLayout(self._width, 2))
550+
if self._direction is not Direction.Input:
551+
members["o"] = wiring.In(data.ArrayLayout(self._width, 2))
552+
members["oe"] = wiring.In(1, init=int(self._direction is Direction.Output))
553+
super().__init__(members)
554+
555+
@property
556+
def direction(self):
557+
return self._direction
558+
559+
@property
560+
def width(self):
561+
return self._width
562+
563+
def __eq__(self, other):
564+
return type(self) == type(other) and self.direction == other.direction and self.width == other.width
565+
566+
def __repr__(self):
567+
return f"DDRBuffer.Signature({self.direction}, {self.width})"
568+
569+
def __init__(self, direction, port, *, i_domain=None, o_domain=None):
570+
if not (hasattr(port, "direction") and isinstance(port.direction, Direction)):
571+
raise TypeError(f"'port' must be a 'SingleEndedPort', 'DifferentialPort', "
572+
f"or a platform-specific object, not {port!r}")
573+
self._port = port
574+
super().__init__(DDRBuffer.Signature(direction, len(port)))
575+
if self.signature.direction is not Direction.Output:
576+
self._i_domain = i_domain or "sync"
577+
elif i_domain is not None:
578+
raise ValueError("Output buffer doesn't have an input domain")
579+
if self.signature.direction is not Direction.Input:
580+
self._o_domain = o_domain or "sync"
581+
elif o_domain is not None:
582+
raise ValueError("Input buffer doesn't have an output domain")
583+
if port.direction is Direction.Input and self.direction is not Direction.Input:
584+
raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
585+
if port.direction is Direction.Output and self.direction is not Direction.Output:
586+
raise ValueError(f"Output port cannot be used with {self.direction.name} buffer")
587+
588+
@property
589+
def port(self):
590+
return self._port
591+
592+
@property
593+
def direction(self):
594+
return self.signature.direction
595+
596+
@property
597+
def i_domain(self):
598+
if self.direction is Direction.Output:
599+
raise AttributeError("Output buffer doesn't have an input domain")
600+
return self._i_domain
601+
602+
@property
603+
def o_domain(self):
604+
if self.direction is Direction.Input:
605+
raise AttributeError("Input buffer doesn't have an output domain")
606+
return self._o_domain
607+
608+
def elaborate(self, platform):
609+
if hasattr(platform, "get_io_buffer"):
610+
res = platform.get_io_buffer(self)
611+
if res is not None:
612+
return res
613+
614+
raise NotImplementedError("DDR buffers cannot be elaborated without a supported platform")
615+
616+
256617
class Pin(wiring.PureInterface):
257618
"""
258619
An interface to an I/O buffer or a group of them that provides uniform access to input, output,

docs/changes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Standard library changes
9595

9696
* Added: :mod:`amaranth.lib.memory`. (`RFC 45`_)
9797
* Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
98+
* Added: :class:`amaranth.lib.io.Buffer`, :class:`amaranth.lib.io.FFBuffer`, :class:`amaranth.lib.io.DDRBuffer`. (`RFC 55`_)
9899
* Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
99100
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
100101
* Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)

0 commit comments

Comments
 (0)