Skip to content

Commit 2c8c9df

Browse files
robtaylorclaude
andcommitted
Phase 6 (Part 1): Create new platform module structure
Create new unified platform/ module to replace platforms/ and steps/: - Created platform/io/ module with signatures, sky130, annotate - Created platform/silicon.py and silicon_step.py - Created platform/base.py with StepBase and _wire_up_ports - Created platform/utils.py with top_components and get_software_builds - Created platform/__init__.py with public API exports - Copied sim, software, board modules (imports need updating) Silicon platform is fully migrated with updated imports. Remaining modules (sim, software, board) are copied but need import updates. Next: Update imports in remaining modules and create backward compat shims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 93a13cb commit 2c8c9df

File tree

16 files changed

+2504
-0
lines changed

16 files changed

+2504
-0
lines changed

chipflow_lib/platform/__init__.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
Platform definitions for ChipFlow.
4+
5+
This module provides platform implementations for silicon, simulation,
6+
and software targets, along with their associated build steps.
7+
"""
8+
9+
# Silicon platform
10+
from .silicon import SiliconPlatformPort, SiliconPlatform
11+
from .silicon_step import SiliconStep, SiliconTop
12+
13+
# Simulation platform (will be added)
14+
# from .sim import SimPlatform
15+
# from .sim_step import SimStep
16+
17+
# Software platform (will be added)
18+
# from .software import SoftwarePlatform
19+
# from .software_step import SoftwareStep
20+
21+
# Board step (will be added)
22+
# from .board_step import BoardStep
23+
24+
# IO signatures and utilities
25+
from .io import (
26+
IO_ANNOTATION_SCHEMA, IOSignature, IOModel, IOTripPoint, IOModelOptions,
27+
OutputIOSignature, InputIOSignature, BidirIOSignature,
28+
JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature,
29+
attach_data, SoftwareDriverSignature, SoftwareBuild,
30+
Sky130DriveMode,
31+
)
32+
33+
# Base classes and utilities
34+
from .base import StepBase, setup_amaranth_tools
35+
from .utils import top_components, get_software_builds
36+
37+
__all__ = [
38+
# Silicon
39+
'SiliconPlatformPort',
40+
'SiliconPlatform',
41+
'SiliconStep',
42+
'SiliconTop',
43+
# IO
44+
'IO_ANNOTATION_SCHEMA',
45+
'IOSignature',
46+
'IOModel',
47+
'IOModelOptions',
48+
'IOTripPoint',
49+
'OutputIOSignature',
50+
'InputIOSignature',
51+
'BidirIOSignature',
52+
'JTAGSignature',
53+
'SPISignature',
54+
'I2CSignature',
55+
'UARTSignature',
56+
'GPIOSignature',
57+
'QSPIFlashSignature',
58+
'attach_data',
59+
'SoftwareDriverSignature',
60+
'SoftwareBuild',
61+
'Sky130DriveMode',
62+
# Base
63+
'StepBase',
64+
'setup_amaranth_tools',
65+
# Utils
66+
'top_components',
67+
'get_software_builds',
68+
]

chipflow_lib/platform/base.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
Base classes and utilities for ChipFlow platform steps.
4+
"""
5+
6+
import logging
7+
import os
8+
9+
from abc import ABC
10+
from amaranth import Module
11+
12+
from .io import IOSignature
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
def setup_amaranth_tools():
18+
"""Configure environment for Amaranth/WASM tools."""
19+
_amaranth_settings = {
20+
"AMARANTH_USE_YOSYS": "system",
21+
"YOSYS": "yowasp-yosys",
22+
"SBY": "yowasp-sby",
23+
"SMTBMC": "yowasp-yosys-smtbmc",
24+
"NEXTPNR_ICE40": "yowasp-nextpnr-ice40",
25+
"ICEPACK": "yowasp-icepackr",
26+
"NEXTPNR_ECP5": "yowasp-nextpnr-ecp5",
27+
"ECPBRAM": "yowasp-ecpbram",
28+
"ECPMULTI": "yowasp-ecpmulti",
29+
"ECPPACK": "yowasp-ecppack",
30+
"ECPPLL": "yowasp-ecppll",
31+
"ECPUNPACK": "yowasp-ecpunpack",
32+
"NEXTPNR-ECP5": "yowasp-nextpnr-ecp5",
33+
"YOSYS-WITNESS": "yowasp-yosys-witness",
34+
}
35+
36+
os.environ |= _amaranth_settings
37+
38+
39+
class StepBase(ABC):
40+
"""Base class for ChipFlow build steps."""
41+
42+
def __init__(self, config={}):
43+
...
44+
45+
def build_cli_parser(self, parser):
46+
"Build the cli parser for this step"
47+
...
48+
49+
def run_cli(self, args):
50+
"Called when this step's is used from `chipflow` command"
51+
self.build()
52+
53+
def build(self, *args):
54+
"builds the design"
55+
...
56+
57+
58+
def _wire_up_ports(m: Module, top, platform):
59+
"""
60+
Wire up component ports to platform ports based on the pin lock.
61+
62+
Args:
63+
m: Amaranth Module to add connections to
64+
top: Dictionary of top-level components
65+
platform: Platform instance with _pinlock and _ports
66+
"""
67+
logger.debug("Wiring up ports")
68+
logger.debug("-> Adding top components:")
69+
for n, t in top.items():
70+
logger.debug(f" > {n}, {t}")
71+
setattr(m.submodules, n, t)
72+
for component, iface in platform._pinlock.port_map.ports.items():
73+
if component.startswith('_'):
74+
logger.debug(f"Ignoring special component {component}")
75+
continue
76+
77+
for iface_name, member, in iface.items():
78+
for name, port in member.items():
79+
logger.debug(f" > {component}, {iface_name}, {name}: {port}")
80+
iface = getattr(top[component], iface_name)
81+
wire = (iface if isinstance(iface.signature, IOSignature)
82+
else getattr(iface, name))
83+
port = platform._ports[port.port_name]
84+
if hasattr(port, 'wire_up'):
85+
port.wire_up(m, wire)
86+
else:
87+
inv_mask = sum(inv << bit for bit, inv in enumerate(port.invert))
88+
if hasattr(wire, 'i'):
89+
m.d.comb += wire.i.eq(port.i ^ inv_mask)
90+
if hasattr(wire, 'o'):
91+
m.d.comb += port.o.eq(wire.o ^ inv_mask)
92+
if hasattr(wire, 'oe'):
93+
m.d.comb += port.oe.eq(wire.oe)
94+
if hasattr(wire, 'ie'):
95+
m.d.comb += port.ie.eq(wire.ie)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
from . import StepBase, setup_amaranth_tools
3+
4+
class BoardStep(StepBase):
5+
"""Build the design for a board."""
6+
7+
def __init__(self, config, platform):
8+
self.platform = platform
9+
setup_amaranth_tools()
10+
11+
def build_cli_parser(self, parser):
12+
pass
13+
14+
def run_cli(self, args):
15+
self.build()
16+
17+
def build(self, *args):
18+
"Build for the given platform"
19+
self.platform.build(*args)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
IO signatures and utilities for ChipFlow platforms.
4+
5+
This module provides IO signature definitions, annotations, and
6+
platform-specific IO utilities.
7+
"""
8+
9+
# IO signature definitions
10+
from .iosignature import (
11+
IOTripPoint,
12+
IOModelOptions,
13+
IOModel,
14+
IO_ANNOTATION_SCHEMA,
15+
IOSignature,
16+
InputIOSignature,
17+
OutputIOSignature,
18+
BidirIOSignature,
19+
_chipflow_schema_uri,
20+
)
21+
22+
# Interface signatures
23+
from .signatures import (
24+
JTAGSignature,
25+
SPISignature,
26+
I2CSignature,
27+
UARTSignature,
28+
GPIOSignature,
29+
QSPIFlashSignature,
30+
attach_data,
31+
SoftwareDriverSignature,
32+
SoftwareBuild,
33+
)
34+
35+
# Sky130-specific
36+
from .sky130 import Sky130DriveMode
37+
38+
# Annotation utilities
39+
from .annotate import amaranth_annotate, submodule_metadata
40+
41+
__all__ = [
42+
# IO Signatures
43+
'IOTripPoint',
44+
'IOModelOptions',
45+
'IOModel',
46+
'IO_ANNOTATION_SCHEMA',
47+
'IOSignature',
48+
'InputIOSignature',
49+
'OutputIOSignature',
50+
'BidirIOSignature',
51+
'_chipflow_schema_uri',
52+
# Interface Signatures
53+
'JTAGSignature',
54+
'SPISignature',
55+
'I2CSignature',
56+
'UARTSignature',
57+
'GPIOSignature',
58+
'QSPIFlashSignature',
59+
'attach_data',
60+
'SoftwareDriverSignature',
61+
'SoftwareBuild',
62+
# Sky130
63+
'Sky130DriveMode',
64+
# Annotations
65+
'amaranth_annotate',
66+
'submodule_metadata',
67+
]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
Amaranth annotation utilities for ChipFlow.
4+
"""
5+
6+
from collections.abc import Generator
7+
from types import MethodType
8+
from typing import (
9+
Tuple, TypeVar,
10+
)
11+
from typing_extensions import is_typeddict
12+
13+
import pydantic
14+
from amaranth import Fragment
15+
from amaranth.lib import meta, wiring
16+
17+
18+
_T_TypedDict = TypeVar('_T_TypedDict')
19+
def amaranth_annotate(modeltype: type[_T_TypedDict], schema_id: str, member: str = '__chipflow_annotation__', decorate_object = False):
20+
# a bit of nastyness as can't set TypedDict as a bound yet
21+
if not is_typeddict(modeltype):
22+
raise TypeError(f"amaranth_annotate must be passed a TypedDict, not {modeltype}")
23+
24+
# interesting pydantic issue gets hit if arbitrary_types_allowed is False
25+
if hasattr(modeltype, '__pydantic_config__'):
26+
config: pydantic.ConfigDict = getattr(modeltype, '__pydantic_config__')
27+
config['arbitrary_types_allowed'] = True
28+
else:
29+
config = pydantic.ConfigDict()
30+
config['arbitrary_types_allowed'] = True
31+
setattr(modeltype, '__pydantic_config__', config)
32+
33+
PydanticModel = pydantic.TypeAdapter(modeltype)
34+
35+
def annotation_schema():
36+
schema = PydanticModel.json_schema()
37+
schema['$schema'] = "https://json-schema.org/draft/2020-12/schema"
38+
schema['$id'] = schema_id
39+
return schema
40+
41+
class Annotation(meta.Annotation):
42+
"Generated annotation class"
43+
schema = annotation_schema()
44+
45+
def __init__(self, parent):
46+
self.parent = parent
47+
48+
@property
49+
def origin(self): # type: ignore
50+
return self.parent
51+
52+
def as_json(self): # type: ignore
53+
# TODO: this is slow, but atm necessary as dump_python doesn't do the appropriate
54+
# transformation of things like PosixPath. Figure out why, maybe log issue/PR with
55+
# pydantic
56+
# return json.loads(PydanticModel.dump_json(getattr(self.parent, member)))
57+
return PydanticModel.dump_python(getattr(self.parent, member), mode='json')
58+
59+
def decorate_class(klass):
60+
if hasattr(klass, 'annotations'):
61+
old_annotations = klass.annotations
62+
else:
63+
old_annotations = None
64+
def annotations(self, obj, /): # type: ignore
65+
if old_annotations:
66+
annotations = old_annotations(self, obj) # type: ignore
67+
else:
68+
annotations = super(klass, obj).annotations(obj)
69+
annotation = Annotation(self)
70+
return annotations + (annotation,) # type: ignore
71+
72+
73+
klass.annotations = annotations
74+
return klass
75+
76+
def decorate_obj(obj):
77+
if hasattr(obj, 'annotations'):
78+
old_annotations = obj.annotations
79+
else:
80+
old_annotations = None
81+
82+
def annotations(self, origin , /): # type: ignore
83+
if old_annotations:
84+
annotations = old_annotations(origin)
85+
else:
86+
annotations = super(obj.__class__, obj).annotations(obj)
87+
annotation = Annotation(self)
88+
return annotations + (annotation,) # type: ignore
89+
90+
setattr(obj, 'annotations', MethodType(annotations, obj))
91+
return obj
92+
93+
if decorate_object:
94+
return decorate_obj
95+
else:
96+
return decorate_class
97+
98+
99+
def submodule_metadata(fragment: Fragment, component_name: str, recursive=False) -> Generator[Tuple[wiring.Component, str| tuple, dict]]:
100+
"""
101+
Generator that finds `component_name` in `fragment` and
102+
then yields the ``wiring.Component``s of that component's submodule, along with their names and metadata
103+
104+
Can only be run once for a given component (or its children)
105+
106+
If recursive = True, then name is a tuple of the heirarchy of names
107+
otherwise, name is the string name of the first level component
108+
"""
109+
110+
subfrag = fragment.find_subfragment(component_name)
111+
design = subfrag.prepare()
112+
for k,v in design.elaboratables.items():
113+
full_name:tuple = design.fragments[design.elaboratables[k]].name
114+
if len(full_name) > 1: # ignore the top component
115+
if recursive:
116+
name = full_name[1:]
117+
else:
118+
if len(full_name) != 2:
119+
continue
120+
name = full_name[1]
121+
if isinstance(k, wiring.Component):
122+
metadata = k.metadata.as_json()['interface']
123+
yield k, name, metadata

0 commit comments

Comments
 (0)