Skip to content

Commit 658fe9b

Browse files
committed
Complete rework of software generation support
This completely reworks software support for a much simpler model for the user. * User needs add only one line to their design to generate software and one line to associate that software with their rom/flash storage * Drivers now live with their IPs. The driver information is communicated via the signature of the component
1 parent 2cbe711 commit 658fe9b

27 files changed

+527
-490
lines changed

chipflow_lib/_doit.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 *-*
2+
# SPDX-License-Identifier: BSD-2-Clause
3+
4+
from typing import List, Tuple, Callable, TypeVar, Generic
5+
from typing_extensions import TypedDict, NotRequired
6+
7+
T=TypeVar('T')
8+
class TaskParams(TypedDict, Generic[T]):
9+
name: str
10+
default: T
11+
short: NotRequired[str]
12+
long: NotRequired[str]
13+
type: NotRequired[Callable[[T], str]]
14+
choices: NotRequired[List[Tuple[str, str]]]
15+
help: NotRequired[str]
16+
inverse: NotRequired[str]
17+
18+

chipflow_lib/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class UnexpectedError(ChipFlowError):
2525
DEFAULT_STEPS = {
2626
"silicon": "chipflow_lib.steps.silicon:SiliconStep",
2727
"sim": "chipflow_lib.steps.sim:SimStep",
28+
"software": "chipflow_lib.steps.software:SoftwareStep"
2829
}
2930

3031

chipflow_lib/platforms/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from .silicon import SiliconPlatformPort, SiliconPlatform
1010
from .sim import SimPlatform
11+
from ._software import SoftwarePlatform
1112
from ._utils import (
1213
IO_ANNOTATION_SCHEMA, IOSignature, IOModel, IOTripPoint, IOModelOptions,
1314
OutputIOSignature, InputIOSignature, BidirIOSignature,
@@ -16,15 +17,15 @@
1617
from ._sky130 import Sky130DriveMode
1718
from ._signatures import (
1819
JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature,
19-
attach_simulation_data
20+
attach_data, DriverSignature, SoftwareBuild
2021
)
2122

2223
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature',
2324
'IOModel', 'IOModelOptions', 'IOTripPoint',
2425
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
2526
'SiliconPlatformPort', 'SiliconPlatform',
26-
'SimPlatform',
27+
'SimPlatform', 'SoftwarePlatform',
2728
'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature', 'QSPIFlashSignature',
28-
'attach_simulation_data',
29+
'attach_data', 'DriverSignature', 'SoftwareBuild',
2930
'Sky130DriveMode',
3031
'PACKAGE_DEFINITIONS']
Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,70 @@
1+
2+
from collections.abc import Generator
13
from types import MethodType
2-
import pydantic
3-
from typing import TypeVar
4+
from typing import (
5+
Tuple, TypeVar,
6+
)
47
from typing_extensions import is_typeddict
5-
_T_TypedDict = TypeVar('_T_TypedDict')
68

7-
def amaranth_annotate(modeltype: type['_T_TypedDict'], schema_id: str, member='__chipflow_annotation__', decorate_object = False):
9+
import pydantic
10+
from amaranth import Fragment
11+
from amaranth.lib import meta, wiring
12+
13+
14+
_T_TypedDict = TypeVar('_T_TypedDict')
15+
def amaranth_annotate(modeltype: type[_T_TypedDict], schema_id: str, member: str = '__chipflow_annotation__', decorate_object = False):
16+
# a bit of nastyness as can't set TypedDict as a bound yet
817
if not is_typeddict(modeltype):
9-
raise TypeError(f'''amaranth_annotate must be passed a TypedDict, not {modeltype}''')
18+
raise TypeError(f"amaranth_annotate must be passed a TypedDict, not {modeltype}")
1019

1120
# interesting pydantic issue gets hit if arbitrary_types_allowed is False
1221
if hasattr(modeltype, '__pydantic_config__'):
13-
config = getattr(modeltype, '__pydantic_config__')
22+
config: pydantic.ConfigDict = getattr(modeltype, '__pydantic_config__')
1423
config['arbitrary_types_allowed'] = True
1524
else:
1625
config = pydantic.ConfigDict()
1726
config['arbitrary_types_allowed'] = True
1827
setattr(modeltype, '__pydantic_config__', config)
28+
1929
PydanticModel = pydantic.TypeAdapter(modeltype)
2030

2131
def annotation_schema():
2232
schema = PydanticModel.json_schema()
23-
schema['$schema'] = 'https://json-schema.org/draft/2020-12/schema'
33+
schema['$schema'] = "https://json-schema.org/draft/2020-12/schema"
2434
schema['$id'] = schema_id
2535
return schema
2636

27-
class Annotation:
28-
'Generated annotation class'
37+
class Annotation(meta.Annotation):
38+
"Generated annotation class"
2939
schema = annotation_schema()
3040

3141
def __init__(self, parent):
3242
self.parent = parent
3343

34-
def origin(self):
44+
@property
45+
def origin(self): # type: ignore
3546
return self.parent
3647

37-
def as_json(self):
38-
return PydanticModel.dump_python(getattr(self.parent, member))
48+
def as_json(self): # type: ignore
49+
# TODO: this is slow, but atm necessary as dump_python doesn't do the appropriate
50+
# transformation of things like PosixPath. Figure out why, maybe log issue/PR with
51+
# pydantic
52+
# return json.loads(PydanticModel.dump_json(getattr(self.parent, member)))
53+
return PydanticModel.dump_python(getattr(self.parent, member), mode='json')
3954

4055
def decorate_class(klass):
4156
if hasattr(klass, 'annotations'):
4257
old_annotations = klass.annotations
4358
else:
4459
old_annotations = None
45-
46-
def annotations(self, obj):
60+
def annotations(self, obj, /): # type: ignore
4761
if old_annotations:
48-
annotations = old_annotations(self, obj)
62+
annotations = old_annotations(self, obj) # type: ignore
4963
else:
5064
annotations = super(klass, obj).annotations(obj)
5165
annotation = Annotation(self)
52-
return annotations + (annotation,)
66+
return annotations + (annotation,) # type: ignore
67+
5368

5469
klass.annotations = annotations
5570
return klass
@@ -60,19 +75,46 @@ def decorate_obj(obj):
6075
else:
6176
old_annotations = None
6277

63-
def annotations(self = None, origin = None):
78+
def annotations(self, origin , /): # type: ignore
6479
if old_annotations:
6580
annotations = old_annotations(origin)
6681
else:
6782
annotations = super(obj.__class__, obj).annotations(obj)
6883
annotation = Annotation(self)
69-
return annotations + (annotation,)
84+
return annotations + (annotation,) # type: ignore
7085

71-
setattr(obj, 'annotations', MethodType(annotations, obj))
86+
setattr(obj, 'annotations', MethodType(annotations, obj))
7287
return obj
7388

7489
if decorate_object:
7590
return decorate_obj
7691
else:
7792
return decorate_class
7893

94+
95+
def submodule_metadata(fragment: Fragment, component_name: str, recursive=False) -> Generator[Tuple[wiring.Component, str| tuple, dict]]:
96+
"""
97+
Generator that finds `component_name` in `fragment` and
98+
then yields the ``wiring.Component``s of that component's submodule, along with their names and metadata
99+
100+
Can only be run once for a given component (or its children)
101+
102+
If recursive = True, then name is a tuple of the heirarchy of names
103+
otherwise, name is the string name of the first level component
104+
"""
105+
106+
subfrag = fragment.find_subfragment(component_name)
107+
design = subfrag.prepare()
108+
for k,v in design.elaboratables.items():
109+
full_name:tuple = design.fragments[design.elaboratables[k]].name
110+
if len(full_name) > 1: # ignore the top component
111+
if recursive:
112+
name = full_name[1:]
113+
else:
114+
if len(full_name) != 2:
115+
continue
116+
name = full_name[1]
117+
if isinstance(k, wiring.Component):
118+
metadata = k.metadata.as_json()['interface']
119+
yield k, name, metadata
120+
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
from .silicon import *
22
from .sim import *
3+
from ._software import SoftwarePlatform
34
from ._utils import *
45
from ._packages import *
6+
57
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature', 'IOModel',
68
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
79
'load_pinlock', "PACKAGE_DEFINITIONS", 'top_components', 'LockFile',
810
'Package', 'PortMap', 'PortDesc', 'Process',
911
'GAPackageDef', 'QuadPackageDef', 'BareDiePackageDef', 'BasePackageDef',
1012
'BringupPins', 'JTAGPins', 'PowerPins',
1113
'SiliconPlatformPort', 'SiliconPlatform',
12-
'SimPlatform']
14+
'SoftwarePlatform', 'SimPlatform',
15+
]

chipflow_lib/platforms/_signatures.py

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,103 @@
11
# SPDX-License-Identifier: BSD-2-Clause
22

33
import re
4-
from typing import List, Tuple, Any
5-
from typing_extensions import Unpack, TypedDict
4+
import sys
5+
6+
from dataclasses import dataclass
7+
from pathlib import Path
8+
from typing import (
9+
List, Tuple, Any, Protocol, runtime_checkable,
10+
Literal, TypeVar, Generic, Annotated
11+
)
12+
13+
from typing_extensions import Unpack, TypedDict, NotRequired
614

715
from amaranth.lib import wiring
816
from amaranth.lib.wiring import Out
17+
from pydantic import PlainSerializer, WithJsonSchema, WrapValidator
918

19+
from .. import _ensure_chipflow_root
1020
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri
1121
from ._annotate import amaranth_annotate
1222

1323
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("simulatable-interface", 0))
14-
SIM_DATA_SCHEMA = str(_chipflow_schema_uri("simulatable-data", 0))
24+
DATA_SCHEMA = str(_chipflow_schema_uri("simulatable-data", 0))
25+
DRIVER_MODEL_SCHEMA = str(_chipflow_schema_uri("driver-model", 0))
1526

1627
class SimInterface(TypedDict):
1728
uid: str
1829
parameters: List[Tuple[str, Any]]
1930

20-
class SimData(TypedDict):
21-
file_name: str
31+
@runtime_checkable
32+
@dataclass
33+
class DataclassProtocol(Protocol):
34+
pass
35+
36+
37+
@dataclass
38+
class SoftwareBuild:
39+
"""
40+
This holds the information needed for building software and providing the built outcome
41+
"""
42+
43+
sources: list[Path]
44+
includes: list[Path]
45+
include_dirs: list[Path]
2246
offset: int
47+
filename: Path
48+
build_dir: Path
49+
type: Literal["SoftwareBuild"] = "SoftwareBuild"
50+
51+
def __init__(self, *, sources: list[Path], includes: list[Path] = [], include_dirs = [], offset=0):
52+
self.build_dir = _ensure_chipflow_root() / 'build' / 'software'
53+
self.filename = self.build_dir / 'software.bin'
54+
self.sources= list(sources)
55+
self.includes = list(includes)
56+
self.include_dirs = list(include_dirs)
57+
self.offset = offset
58+
59+
60+
_T_DataClass = TypeVar('_T_DataClass', bound=DataclassProtocol)
61+
class Data(TypedDict, Generic[_T_DataClass]):
62+
data: _T_DataClass
63+
64+
65+
class DriverModel(TypedDict):
66+
"""
67+
Options for `DriverSignature`
68+
69+
Attributes:
70+
component: The `wiring.Component` that this is the signature for.
71+
regs_struct: The name of the C struct that represents the registers of this component
72+
h_files: Header files for the driver
73+
c_files: C files for the driver
74+
regs_bus: The bus of this `Component` which contains its control registers
75+
include_dirs: any extra include directories needed by the driver
76+
"""
77+
# we just extrat the info we need, don't actually serialise a `wiring.Component`...
78+
component: Annotated[
79+
wiring.Component,
80+
PlainSerializer(lambda x: {
81+
'name': x.__class__.__name__,
82+
'file': sys.modules[x.__module__].__file__
83+
}, return_type=dict),
84+
WithJsonSchema({
85+
'type': 'object',
86+
'properties': {
87+
'name': { 'type': 'string' },
88+
'file': { 'type': 'string' },
89+
}
90+
}),
91+
WrapValidator(lambda v, h: v) # Don't care about it actually..
92+
] | dict
93+
94+
regs_struct: str
95+
h_files: NotRequired[list[Path]]
96+
c_files: NotRequired[list[Path]]
97+
include_dirs: NotRequired[list[Path]]
98+
regs_bus: NotRequired[str]
99+
_base_path: NotRequired[Path] # gets filled by the decorator to the base directory where the Component was defined
100+
23101

24102
_VALID_UID = re.compile('[a-zA-Z_.]').search
25103

@@ -124,7 +202,31 @@ def __chipflow_parameters__(self):
124202
return [('pin_count',self._pin_count)]
125203

126204

127-
def attach_simulation_data(c: wiring.Component, **kwargs: Unpack[SimData]):
128-
setattr(c.signature, '__chipflow_simulation_data__', kwargs)
129-
amaranth_annotate(SimData, SIM_DATA_SCHEMA, '__chipflow_simulation_data__', decorate_object=True)(c.signature)
205+
def attach_data(external_interface: wiring.PureInterface, component: wiring.Component, data: DataclassProtocol):
206+
data_dict: Data = {'data':data}
207+
setattr(component.signature, '__chipflow_data__', data_dict)
208+
amaranth_annotate(Data, DATA_SCHEMA, '__chipflow_data__', decorate_object=True)(component.signature)
209+
setattr(external_interface.signature, '__chipflow_data__', data_dict)
210+
amaranth_annotate(Data, DATA_SCHEMA, '__chipflow_data__', decorate_object=True)(external_interface.signature)
211+
212+
213+
class DriverSignature(wiring.Signature):
214+
215+
def __init__(self, members, **kwargs: Unpack[DriverModel]):
216+
definition_file = sys.modules[kwargs['component'].__module__].__file__
217+
assert definition_file
218+
base_path = Path(definition_file).parent.absolute()
219+
kwargs['_base_path'] = base_path
220+
if 'regs_bus' not in kwargs:
221+
kwargs['regs_bus'] = 'bus'
222+
223+
# execute any generators here
224+
for k in ('c_files', 'h_files', 'includedirs'):
225+
if k in kwargs:
226+
kwargs[k] = list(kwargs[k]) #type: ignore
227+
228+
self.__chipflow_driver_model__ = kwargs
229+
amaranth_annotate(DriverModel, DRIVER_MODEL_SCHEMA, '__chipflow_driver_model__', decorate_object=True)(self)
230+
super().__init__(members=members)
231+
130232

0 commit comments

Comments
 (0)