Skip to content

Commit f09c2f4

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 f39d8d8 commit f09c2f4

36 files changed

+969
-879
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/_pin_lock.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from pprint import pformat
77

88
from . import _parse_config, _ensure_chipflow_root, ChipFlowError
9-
from .platforms._internal import top_components, LockFile, PACKAGE_DEFINITIONS
9+
from .platforms._utils import top_components, LockFile
10+
from .platforms._packages import PACKAGE_DEFINITIONS
1011

1112
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
1213
logger = logging.getLogger(__name__)

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/config_models.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
11
# SPDX-License-Identifier: BSD-2-Clause
2-
from typing import Dict, Optional, Any, List
2+
from enum import Enum
3+
from typing import Dict, Optional, Any, List, Annotated
34

4-
from pydantic import BaseModel
5+
from pydantic import (
6+
BaseModel, PlainSerializer, WrapValidator
7+
)
58

6-
from .platforms._internal import PACKAGE_DEFINITIONS, Process, Voltage
9+
from ._appresponse import AppResponseModel, OmitIfNone
710

11+
class Process(Enum):
12+
"""
13+
IC manufacturing process
14+
"""
15+
#: Skywater foundry open-source 130nm process
16+
SKY130 = "sky130"
17+
#: GlobalFoundries open-source 130nm process
18+
GF180 = "gf180"
19+
#: Pragmatic Semiconductor FlexIC process (old)
20+
HELVELLYN2 = "helvellyn2"
21+
#: GlobalFoundries 130nm BCD process
22+
GF130BCD = "gf130bcd"
23+
#: IHP open source 130nm SiGe Bi-CMOS process
24+
IHP_SG13G2 = "ihp_sg13g2"
825

9-
def known_package(package: str):
10-
if package not in PACKAGE_DEFINITIONS.keys():
11-
raise ValueError(f"{package} is not a valid package type. Valid package types are {PACKAGE_DEFINITIONS.keys()}")
26+
def __str__(self):
27+
return f'{self.value}'
28+
29+
30+
31+
Voltage = Annotated[
32+
float,
33+
PlainSerializer(lambda x: f'{x:.1e}V', return_type=str),
34+
WrapValidator(lambda v, h: h(v.strip('Vv ') if isinstance(v, str) else h(v)))
35+
]
36+
37+
38+
class VoltageRange(AppResponseModel):
39+
"""
40+
Models a voltage range for a power domain or IO
41+
"""
42+
min: Annotated[Optional[Voltage], OmitIfNone()] = None
43+
max: Annotated[Optional[Voltage], OmitIfNone()] = None
44+
typical: Annotated[Optional[Voltage], OmitIfNone()] = None
1245

1346

1447
class SiliconConfig(BaseModel):
@@ -22,13 +55,21 @@ class SiliconConfig(BaseModel):
2255
class SimulationConfig(BaseModel):
2356
num_steps: int = 3000000
2457

58+
class CompilerConfig(BaseModel):
59+
cpu: str
60+
abi: str
61+
62+
class SoftwareConfig(BaseModel):
63+
riscv: CompilerConfig = CompilerConfig(cpu="baseline_rv32-a-c-d", abi="ilp32")
64+
2565
class ChipFlowConfig(BaseModel):
2666
"""Root configuration for chipflow.toml."""
2767
project_name: str
2868
top: Dict[str, Any] = {}
2969
steps: Optional[Dict[str, str]] = None
3070
silicon: Optional[SiliconConfig] = None
3171
simulation: SimulationConfig = SimulationConfig()
72+
software: SoftwareConfig = SoftwareConfig()
3273
clock_domains: Optional[List[str]] = None
3374

3475

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+

chipflow_lib/platforms/_internal.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

chipflow_lib/platforms/_openframe.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import List, NamedTuple, Optional, Literal
22

3-
from ._utils import Voltage, PowerPins, LinearAllocPackageDef, BringupPins
3+
from ._utils import PowerPins, LinearAllocPackageDef, BringupPins
4+
from ..config_models import Voltage
45

56
class OFPin(NamedTuple):
67
pin: int

0 commit comments

Comments
 (0)