1414from typing import (
1515 Any , Dict , List , Set ,
1616 Tuple , Optional , Union , Literal ,
17- Annotated , NamedTuple
17+ Annotated , NamedTuple , Self
1818)
1919from typing_extensions import (
2020 TypedDict , Unpack , NotRequired
2121)
2222
2323
24- from amaranth import Const
24+ from amaranth import Const , ClockDomain , Fragment
2525from amaranth .lib import wiring , io , meta
2626from amaranth .lib .wiring import In , Out
2727from pydantic import (
@@ -72,7 +72,7 @@ class VoltageRange:
7272 "required" : ["width" , "signed" , "value" ]
7373})
7474
75- @with_config (ConfigDict (use_enum_values = True , arbitrary_types_allowed = True ))
75+ @with_config (ConfigDict (use_enum_values = True , arbitrary_types_allowed = True )) # type: ignore[reportCallIssue]
7676class IOModel (TypedDict ):
7777 """
7878 Options for IO Ports
@@ -82,8 +82,10 @@ class IOModel(TypedDict):
8282 all_have_oe: controls whether each output wire is associated with an individual Output Enable bit
8383 or a single OE bit will be used for entire port, the default value is False, indicating that a
8484 single OE bit controls the entire port.
85- allocate_power: Whether a power line should be allocated with this interface
85+ allocate_power: Whether a power line should be allocated with this interface. NB there is only one of these, so IO with multiple IO power domains must be split up.
8686 power_voltage: Voltage range of the allocated power
87+ clock_domain_i: the name of the `Amaranth.ClockDomain` for input. NB there is only one of these, so IO with multiple input clocks must be split up.
88+ clock_domain_o: the name of the `Amaranth.ClockDomain` for output. NB there is only one of these, so IO with multiple output clocks must be split up.
8789 init: a :ref:`Const` value for the initial values of the port
8890 """
8991
@@ -92,8 +94,11 @@ class IOModel(TypedDict):
9294 all_have_oe : NotRequired [bool ]
9395 allocate_power : NotRequired [bool ]
9496 power_voltage : NotRequired [VoltageRange ]
97+ clock_domain_i : NotRequired [str ]
98+ clock_domain_o : NotRequired [str ]
9599 init : NotRequired [Annotated [Const , ConstSerializer , ConstSchema ]]
96100
101+
97102def io_annotation_schema ():
98103 PydanticModel = TypeAdapter (IOModel )
99104 schema = PydanticModel .json_schema ()
@@ -144,6 +149,12 @@ def __init__(self, **kwargs: Unpack[IOModel]):
144149 sig = {"o" : Out (width )}
145150 case _:
146151 assert False
152+
153+ if 'clock_domain_i' not in model :
154+ model ['clock_domain_i' ] = 'sync'
155+ if 'clock_domain_o' not in model :
156+ model ['clock_domain_o' ] = 'sync'
157+
147158 self ._model = model
148159 super ().__init__ (sig )
149160
@@ -382,7 +393,7 @@ def _count_member_pins(name: str, member: Dict[str, Any]) -> int:
382393 return 0
383394
384395
385- def _allocate_pins (name : str , member : Dict [str , Any ], pins : List [str ], port_name : Optional [str ] = None ) -> Tuple [Dict [str , Port ], List [str ]]:
396+ def _allocate_pins (name : str , member : Dict [str , Any ], pins : List [Pin ], port_name : Optional [str ] = None ) -> Tuple [Dict [str , Port ], List [Pin ]]:
386397 "Allocate pins based of Amaranth member metadata"
387398
388399 if port_name is None :
@@ -459,7 +470,7 @@ class LockFile(pydantic.BaseModel):
459470 Representation of a pin lock file.
460471
461472 Attributes:
462- package: Information about package, power, clocks, reset etc
473+ package: Information about the physical package
463474 port_map: Mapping of components to interfaces to port
464475 metadata: Amaranth metadata, for reference
465476 """
@@ -474,9 +485,10 @@ class LockFile(pydantic.BaseModel):
474485class Package (pydantic .BaseModel ):
475486 """
476487 Serialisable identifier for a defined packaging option
488+ Attributes:
489+ type: Package type
477490 """
478491 type : PackageDef = pydantic .Field (discriminator = "package_type" )
479- clocks : List [str ]
480492
481493def _linear_allocate_components (interfaces : dict , lockfile : LockFile | None , allocate , unallocated ) -> PortMap :
482494 port_map = PortMap ()
@@ -528,7 +540,8 @@ class BasePackageDef(pydantic.BaseModel, abc.ABC):
528540 name : str
529541
530542 def model_post_init (self , __context ):
531- self ._interfaces = {}
543+ self ._interfaces : Dict [str , dict ] = {}
544+ self ._components : Dict [str , wiring .Component ] = {}
532545 return super ().model_post_init (__context )
533546
534547 def register_component (self , name : str , component : wiring .Component ) -> None :
@@ -539,8 +552,12 @@ def register_component(self, name: str, component: wiring.Component) -> None:
539552 component: Amaranth `wiring.Component` to allocate
540553
541554 """
555+ self ._components [name ] = component
542556 self ._interfaces [name ] = component .metadata .as_json ()
543557
558+ def _get_package (self ) -> Package :
559+ assert self is not Self
560+ return Package (type = self ) # type: ignore
544561
545562 @abc .abstractmethod
546563 def allocate_pins (self , process : 'Process' , lockfile : Optional [LockFile ]) -> LockFile :
@@ -595,8 +612,8 @@ def model_post_init(self, __context):
595612
596613 def allocate_pins (self , process : 'Process' , lockfile : LockFile | None ) -> LockFile :
597614 portmap = _linear_allocate_components (self ._interfaces , lockfile , self ._allocate , set (self ._ordered_pins ))
598- #clocks =
599- return LockFile (package = Package ( type = self ) , process = process , metadata = self ._interfaces , port_map = portmap )
615+ package = self . _get_package ()
616+ return LockFile (package = package , process = process , metadata = self ._interfaces , port_map = portmap )
600617
601618 @property
602619 def bringup_pins (self ) -> BringupPins :
@@ -676,8 +693,8 @@ def model_post_init(self, __context):
676693
677694 def allocate_pins (self , process : 'Process' , lockfile : LockFile | None ) -> 'LockFile' :
678695 portmap = _linear_allocate_components (self ._interfaces , lockfile , self ._allocate , set (self ._ordered_pins ))
679- return LockFile ( package = Package ( type = self ), process = process , metadata = self ._interfaces , port_map = portmap )
680-
696+ package = self ._get_package ( )
697+ return LockFile ( package = package , process = process , metadata = self . _interfaces , port_map = portmap )
681698
682699 def _allocate (self , available : Set [int ], width : int ) -> List [Pin ]:
683700 avail_n : List [Pin ] = sorted (available )
@@ -888,7 +905,8 @@ def sort_by_quadrant(pins: Set[GAPin]) -> List[Pin]:
888905
889906 def allocate_pins (self , process : 'Process' , lockfile : LockFile | None ) -> 'LockFile' :
890907 portmap = _linear_allocate_components (self ._interfaces , lockfile , self ._allocate , set (self ._ordered_pins ))
891- return LockFile (package = Package (type = self ), process = process , metadata = self ._interfaces , port_map = portmap )
908+ package = self ._get_package ()
909+ return LockFile (package = package , process = process , metadata = self ._interfaces , port_map = portmap )
892910
893911 def _allocate (self , available : Set [Pin ], width : int ) -> List [Pin ]:
894912 avail_n = sorted (available )
0 commit comments