1
1
import enum
2
2
import operator
3
+ import warnings
3
4
from abc import ABCMeta , abstractmethod
4
5
from collections .abc import Iterable
5
6
11
12
12
13
13
14
__all__ = [
14
- "Direction" , "PortLike" , "SingleEndedPort" , "DifferentialPort" ,
15
+ "Direction" , "PortLike" , "SingleEndedPort" , "DifferentialPort" , "SimulationPort" ,
15
16
"Buffer" , "FFBuffer" , "DDRBuffer" ,
16
17
"Pin" ,
17
18
]
@@ -57,6 +58,12 @@ class PortLike(metaclass=ABCMeta):
57
58
:class:`amaranth.hdl.IOPort` is not an instance of :class:`amaranth.lib.io.PortLike`.
58
59
"""
59
60
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
+
60
67
@property
61
68
@abstractmethod
62
69
def direction (self ):
@@ -108,6 +115,32 @@ def __invert__(self):
108
115
"""
109
116
raise NotImplementedError # :nocov:
110
117
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
+
111
144
112
145
class SingleEndedPort (PortLike ):
113
146
"""Represents a single-ended library I/O port.
@@ -124,9 +157,9 @@ class SingleEndedPort(PortLike):
124
157
same length as the width of :py:`io`, and the inversion is specified for individual wires.
125
158
direction : :class:`Direction` or :class:`str`
126
159
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.
130
163
131
164
Attributes
132
165
----------
@@ -176,27 +209,6 @@ def __getitem__(self, index):
176
209
direction = self ._direction )
177
210
178
211
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
- """
200
212
if not isinstance (other , SingleEndedPort ):
201
213
return NotImplemented
202
214
return SingleEndedPort (Cat (self ._io , other ._io ), invert = self ._invert + other ._invert ,
@@ -231,9 +243,9 @@ class DifferentialPort(PortLike):
231
243
individual wires.
232
244
direction : :class:`Direction` or :class:`str`
233
245
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.
237
249
238
250
Attributes
239
251
----------
@@ -293,27 +305,6 @@ def __getitem__(self, index):
293
305
direction = self ._direction )
294
306
295
307
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
- """
317
308
if not isinstance (other , DifferentialPort ):
318
309
return NotImplemented
319
310
return DifferentialPort (Cat (self ._p , other ._p ), Cat (self ._n , other ._n ),
@@ -331,6 +322,167 @@ def __repr__(self):
331
322
f"direction={ self ._direction } )" )
332
323
333
324
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
+
334
486
class Buffer (wiring .Component ):
335
487
"""A combinational I/O buffer component.
336
488
@@ -476,6 +628,18 @@ def elaborate(self, platform):
476
628
else :
477
629
m .submodules += IOBufferInstance (self ._port .p , o = o_inv , oe = self .oe , i = i_inv )
478
630
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 )))
479
643
else :
480
644
raise TypeError ("Cannot elaborate generic 'Buffer' with port {self._port!r}" ) # :nocov:
481
645
@@ -719,6 +883,12 @@ class DDRBuffer(wiring.Component):
719
883
720
884
This limitation may be lifted in the future.
721
885
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
+
722
892
Parameters
723
893
----------
724
894
direction : :class:`Direction`
@@ -826,6 +996,9 @@ def elaborate(self, platform):
826
996
if hasattr (platform , "get_io_buffer" ):
827
997
return platform .get_io_buffer (self )
828
998
999
+ if isinstance (self ._port , SimulationPort ):
1000
+ raise NotImplementedError (f"DDR buffers are not supported in simulation" ) # :nocov:
1001
+
829
1002
raise NotImplementedError (f"DDR buffers are not supported on { platform !r} " ) # :nocov:
830
1003
831
1004
0 commit comments