11import enum
22import operator
3+ import warnings
34from abc import ABCMeta , abstractmethod
45from collections .abc import Iterable
56
1112
1213
1314__all__ = [
14- "Direction" , "PortLike" , "SingleEndedPort" , "DifferentialPort" ,
15+ "Direction" , "PortLike" , "SingleEndedPort" , "DifferentialPort" , "SimulationPort" ,
1516 "Buffer" , "FFBuffer" , "DDRBuffer" ,
1617 "Pin" ,
1718]
@@ -57,6 +58,12 @@ class PortLike(metaclass=ABCMeta):
5758 :class:`amaranth.hdl.IOPort` is not an instance of :class:`amaranth.lib.io.PortLike`.
5859 """
5960
61+ # TODO(amaranth-0.6): remove
62+ def __init_subclass__ (cls ):
63+ if cls .__add__ is PortLike .__add__ :
64+ warnings .warn (f"{ cls .__module__ } .{ cls .__qualname__ } must override the `__add__` method" ,
65+ DeprecationWarning , stacklevel = 2 )
66+
6067 @property
6168 @abstractmethod
6269 def direction (self ):
@@ -108,6 +115,32 @@ def __invert__(self):
108115 """
109116 raise NotImplementedError # :nocov:
110117
118+ # TODO(amaranth-0.6): make abstract
119+ # @abstractmethod
120+ def __add__ (self , other ):
121+ """Concatenates two library I/O ports of the same type.
122+
123+ The direction of the resulting port is:
124+
125+ * The same as the direction of both, if the two ports have the same direction.
126+ * :attr:`Direction.Input` if a bidirectional port is concatenated with an input port.
127+ * :attr:`Direction.Output` if a bidirectional port is concatenated with an output port.
128+
129+ Returns
130+ -------
131+ :py:`type(self)`
132+ A new :py:`type(self)` which contains wires from :py:`self` followed by wires
133+ from :py:`other`, preserving their polarity inversion.
134+
135+ Raises
136+ ------
137+ :exc:`ValueError`
138+ If an input port is concatenated with an output port.
139+ :exc:`TypeError`
140+ If :py:`self` and :py:`other` have different types.
141+ """
142+ raise NotImplementedError # :nocov:
143+
111144
112145class SingleEndedPort (PortLike ):
113146 """Represents a single-ended library I/O port.
@@ -124,9 +157,9 @@ class SingleEndedPort(PortLike):
124157 same length as the width of :py:`io`, and the inversion is specified for individual wires.
125158 direction : :class:`Direction` or :class:`str`
126159 Set of allowed buffer directions. A string is converted to a :class:`Direction` first.
127- If equal to :attr:`Direction.Input` or :attr:`Direction.Output`, this port can only be used
128- with buffers of matching direction. If equal to :attr:`Direction.Bidir`, this port can be
129- used with buffers of any direction.
160+ If equal to :attr:`~ Direction.Input` or :attr:`~ Direction.Output`, this port can only be
161+ used with buffers of matching direction. If equal to :attr:`~ Direction.Bidir`, this port
162+ can be used with buffers of any direction.
130163
131164 Attributes
132165 ----------
@@ -176,27 +209,6 @@ def __getitem__(self, index):
176209 direction = self ._direction )
177210
178211 def __add__ (self , other ):
179- """Concatenates two single-ended library I/O ports.
180-
181- The direction of the resulting port is:
182-
183- * The same as the direction of both, if the two ports have the same direction.
184- * :attr:`Direction.Input` if a bidirectional port is concatenated with an input port.
185- * :attr:`Direction.Output` if a bidirectional port is concatenated with an output port.
186-
187- Returns
188- -------
189- :class:`SingleEndedPort`
190- A new :class:`SingleEndedPort` which contains wires from :py:`self` followed by wires
191- from :py:`other`, preserving their polarity inversion.
192-
193- Raises
194- ------
195- :exc:`ValueError`
196- If an input port is concatenated with an output port.
197- :exc:`TypeError`
198- If :py:`self` and :py:`other` have incompatible types.
199- """
200212 if not isinstance (other , SingleEndedPort ):
201213 return NotImplemented
202214 return SingleEndedPort (Cat (self ._io , other ._io ), invert = self ._invert + other ._invert ,
@@ -231,9 +243,9 @@ class DifferentialPort(PortLike):
231243 individual wires.
232244 direction : :class:`Direction` or :class:`str`
233245 Set of allowed buffer directions. A string is converted to a :class:`Direction` first.
234- If equal to :attr:`Direction.Input` or :attr:`Direction.Output`, this port can only be used
235- with buffers of matching direction. If equal to :attr:`Direction.Bidir`, this port can be
236- used with buffers of any direction.
246+ If equal to :attr:`~ Direction.Input` or :attr:`~ Direction.Output`, this port can only be
247+ used with buffers of matching direction. If equal to :attr:`~ Direction.Bidir`, this port
248+ can be used with buffers of any direction.
237249
238250 Attributes
239251 ----------
@@ -293,27 +305,6 @@ def __getitem__(self, index):
293305 direction = self ._direction )
294306
295307 def __add__ (self , other ):
296- """Concatenates two differential library I/O ports.
297-
298- The direction of the resulting port is:
299-
300- * The same as the direction of both, if the two ports have the same direction.
301- * :attr:`Direction.Input` if a bidirectional port is concatenated with an input port.
302- * :attr:`Direction.Output` if a bidirectional port is concatenated with an output port.
303-
304- Returns
305- -------
306- :class:`DifferentialPort`
307- A new :class:`DifferentialPort` which contains pairs of wires from :py:`self` followed
308- by pairs of wires from :py:`other`, preserving their polarity inversion.
309-
310- Raises
311- ------
312- :exc:`ValueError`
313- If an input port is concatenated with an output port.
314- :exc:`TypeError`
315- If :py:`self` and :py:`other` have incompatible types.
316- """
317308 if not isinstance (other , DifferentialPort ):
318309 return NotImplemented
319310 return DifferentialPort (Cat (self ._p , other ._p ), Cat (self ._n , other ._n ),
@@ -331,6 +322,167 @@ def __repr__(self):
331322 f"direction={ self ._direction } )" )
332323
333324
325+ class SimulationPort (PortLike ):
326+ """Represents a simulation library I/O port.
327+
328+ Implements the :class:`PortLike` interface.
329+
330+ Parameters
331+ ----------
332+ direction : :class:`Direction` or :class:`str`
333+ Set of allowed buffer directions. A string is converted to a :class:`Direction` first.
334+ If equal to :attr:`~Direction.Input` or :attr:`~Direction.Output`, this port can only be
335+ used with buffers of matching direction. If equal to :attr:`~Direction.Bidir`, this port
336+ can be used with buffers of any direction.
337+ width : :class:`int`
338+ Width of the port. The width of each of the attributes :py:`i`, :py:`o`, :py:`oe` (whenever
339+ present) equals :py:`width`.
340+ invert : :class:`bool` or iterable of :class:`bool`
341+ Polarity inversion. If the value is a simple :class:`bool`, it specifies inversion for
342+ the entire port. If the value is an iterable of :class:`bool`, the iterable must have the
343+ same length as the width of :py:`p` and :py:`n`, and the inversion is specified for
344+ individual wires.
345+ name : :class:`str` or :py:`None`
346+ Name of the port. This name is only used to derive the names of the input, output, and
347+ output enable signals.
348+ src_loc_at : :class:`int`
349+ :ref:`Source location <lang-srcloc>`. Used to infer :py:`name` if not specified.
350+
351+ Attributes
352+ ----------
353+ i : :class:`Signal`
354+ Input signal. Present if :py:`direction in (Input, Bidir)`.
355+ o : :class:`Signal`
356+ Ouptut signal. Present if :py:`direction in (Output, Bidir)`.
357+ oe : :class:`Signal`
358+ Output enable signal. Present if :py:`direction in (Output, Bidir)`.
359+ invert : :class:`tuple` of :class:`bool`
360+ The :py:`invert` parameter, normalized to specify polarity inversion per-wire.
361+ direction : :class:`Direction`
362+ The :py:`direction` parameter, normalized to the :class:`Direction` enumeration.
363+ """
364+ def __init__ (self , direction , width , * , invert = False , name = None , src_loc_at = 0 ):
365+ if name is not None and not isinstance (name , str ):
366+ raise TypeError (f"Name must be a string, not { name !r} " )
367+ if name is None :
368+ name = tracer .get_var_name (depth = 2 + src_loc_at , default = "$port" )
369+
370+ if not (isinstance (width , int ) and width >= 0 ):
371+ raise TypeError (f"Width must be a non-negative integer, not { width !r} " )
372+
373+ self ._direction = Direction (direction )
374+
375+ self ._i = self ._o = self ._oe = None
376+ if self ._direction in (Direction .Input , Direction .Bidir ):
377+ self ._i = Signal (width , name = f"{ name } __i" )
378+ if self ._direction in (Direction .Output , Direction .Bidir ):
379+ self ._o = Signal (width , name = f"{ name } __o" )
380+ self ._oe = Signal (width , name = f"{ name } __oe" ,
381+ init = ~ 0 if self ._direction is Direction .Output else 0 )
382+
383+ if isinstance (invert , bool ):
384+ self ._invert = (invert ,) * width
385+ elif isinstance (invert , Iterable ):
386+ self ._invert = tuple (invert )
387+ if len (self ._invert ) != width :
388+ raise ValueError (f"Length of 'invert' ({ len (self ._invert )} ) doesn't match "
389+ f"port width ({ width } )" )
390+ if not all (isinstance (item , bool ) for item in self ._invert ):
391+ raise TypeError (f"'invert' must be a bool or iterable of bool, not { invert !r} " )
392+ else :
393+ raise TypeError (f"'invert' must be a bool or iterable of bool, not { invert !r} " )
394+
395+ @property
396+ def i (self ):
397+ if self ._i is None :
398+ raise AttributeError (
399+ "Simulation port with output direction does not have an input signal" )
400+ return self ._i
401+
402+ @property
403+ def o (self ):
404+ if self ._o is None :
405+ raise AttributeError (
406+ "Simulation port with input direction does not have an output signal" )
407+ return self ._o
408+
409+ @property
410+ def oe (self ):
411+ if self ._oe is None :
412+ raise AttributeError (
413+ "Simulation port with input direction does not have an output enable signal" )
414+ return self ._oe
415+
416+ @property
417+ def invert (self ):
418+ return self ._invert
419+
420+ @property
421+ def direction (self ):
422+ return self ._direction
423+
424+ def __len__ (self ):
425+ if self ._direction is Direction .Input :
426+ return len (self ._i )
427+ if self ._direction is Direction .Output :
428+ assert len (self ._o ) == len (self ._oe )
429+ return len (self ._o )
430+ if self ._direction is Direction .Bidir :
431+ assert len (self ._i ) == len (self ._o ) == len (self ._oe )
432+ return len (self ._i )
433+ assert False # :nocov:
434+
435+ def __getitem__ (self , key ):
436+ result = object .__new__ (type (self ))
437+ result ._i = None if self ._i is None else self ._i [key ]
438+ result ._o = None if self ._o is None else self ._o [key ]
439+ result ._oe = None if self ._oe is None else self ._oe [key ]
440+ if isinstance (key , slice ):
441+ result ._invert = self ._invert [key ]
442+ else :
443+ result ._invert = (self ._invert [key ],)
444+ result ._direction = self ._direction
445+ return result
446+
447+ def __invert__ (self ):
448+ result = object .__new__ (type (self ))
449+ result ._i = self ._i
450+ result ._o = self ._o
451+ result ._oe = self ._oe
452+ result ._invert = tuple (not invert for invert in self ._invert )
453+ result ._direction = self ._direction
454+ return result
455+
456+ def __add__ (self , other ):
457+ if not isinstance (other , SimulationPort ):
458+ return NotImplemented
459+ direction = self ._direction & other ._direction
460+ result = object .__new__ (type (self ))
461+ result ._i = None if direction is Direction .Output else Cat (self ._i , other ._i )
462+ result ._o = None if direction is Direction .Input else Cat (self ._o , other ._o )
463+ result ._oe = None if direction is Direction .Input else Cat (self ._oe , other ._oe )
464+ result ._invert = self ._invert + other ._invert
465+ result ._direction = direction
466+ return result
467+
468+ def __repr__ (self ):
469+ parts = []
470+ if self ._i is not None :
471+ parts .append (f"i={ self ._i !r} " )
472+ if self ._o is not None :
473+ parts .append (f"o={ self ._o !r} " )
474+ if self ._oe is not None :
475+ parts .append (f"oe={ self ._oe !r} " )
476+ if not any (self ._invert ):
477+ invert = False
478+ elif all (self ._invert ):
479+ invert = True
480+ else :
481+ invert = self ._invert
482+ return (f"SimulationPort({ ', ' .join (parts )} , invert={ invert !r} , "
483+ f"direction={ self ._direction } )" )
484+
485+
334486class Buffer (wiring .Component ):
335487 """A combinational I/O buffer component.
336488
@@ -476,6 +628,18 @@ def elaborate(self, platform):
476628 else :
477629 m .submodules += IOBufferInstance (self ._port .p , o = o_inv , oe = self .oe , i = i_inv )
478630 m .submodules += IOBufferInstance (self ._port .n , o = ~ o_inv , oe = self .oe )
631+ elif isinstance (self ._port , SimulationPort ):
632+ if self .direction is Direction .Bidir :
633+ # Loop back `o` if `oe` is asserted. This frees the test harness from having to
634+ # provide this functionality itself.
635+ for i_inv_bit , oe_bit , o_bit , i_bit in \
636+ zip (i_inv , self ._port .oe , self ._port .o , self ._port .i ):
637+ m .d .comb += i_inv_bit .eq (Cat (Mux (oe_bit , o_bit , i_bit )))
638+ if self .direction is Direction .Input :
639+ m .d .comb += i_inv .eq (self ._port .i )
640+ if self .direction in (Direction .Output , Direction .Bidir ):
641+ m .d .comb += self ._port .o .eq (o_inv )
642+ m .d .comb += self ._port .oe .eq (self .oe .replicate (len (self ._port )))
479643 else :
480644 raise TypeError ("Cannot elaborate generic 'Buffer' with port {self._port!r}" ) # :nocov:
481645
@@ -719,6 +883,12 @@ class DDRBuffer(wiring.Component):
719883
720884 This limitation may be lifted in the future.
721885
886+ .. warning::
887+
888+ Double data rate I/O buffers are not compatible with :class:`SimulationPort`.
889+
890+ This limitation may be lifted in the future.
891+
722892 Parameters
723893 ----------
724894 direction : :class:`Direction`
@@ -826,6 +996,9 @@ def elaborate(self, platform):
826996 if hasattr (platform , "get_io_buffer" ):
827997 return platform .get_io_buffer (self )
828998
999+ if isinstance (self ._port , SimulationPort ):
1000+ raise NotImplementedError (f"DDR buffers are not supported in simulation" ) # :nocov:
1001+
8291002 raise NotImplementedError (f"DDR buffers are not supported on { platform !r} " ) # :nocov:
8301003
8311004
0 commit comments