Skip to content

Commit bc0a3c2

Browse files
committed
wip
1 parent 0ac2998 commit bc0a3c2

File tree

4 files changed

+56
-33
lines changed

4 files changed

+56
-33
lines changed

chipflow_lib/config_models.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# SPDX-License-Identifier: BSD-2-Clause
22
import re
3-
from typing import Dict, Optional, Literal, Any
3+
from typing import Dict, Optional, Literal, Any, List
44

55
from pydantic import BaseModel, model_validator, ValidationInfo, field_validator
66

77
from .platforms.utils import Process
88

9-
109
class PadConfig(BaseModel):
1110
"""Configuration for a pad in chipflow.toml."""
1211
type: Literal["io", "i", "o", "oe", "clock", "reset", "power", "ground"]
@@ -72,8 +71,7 @@ class ChipFlowConfig(BaseModel):
7271
top: Dict[str, Any] = {}
7372
steps: Optional[Dict[str, str]] = None
7473
silicon: Optional[SiliconConfig] = None
75-
clocks: Optional[Dict[str, str]] = None
76-
resets: Optional[Dict[str, str]] = None
74+
clock_domains: Optional[List[str]] = None
7775

7876

7977
class Config(BaseModel):

chipflow_lib/platforms/silicon.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(self,
7373
invert: bool = False):
7474
self._direction = io.Direction(port.direction)
7575
self._invert = invert
76-
self._options = port.options
76+
self._model = port.model
7777
self._pins = port.pins
7878

7979
# Initialize signal attributes to None

chipflow_lib/platforms/utils.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
from typing import (
1515
Any, Dict, List, Set,
1616
Tuple, Optional, Union, Literal,
17-
Annotated, NamedTuple
17+
Annotated, NamedTuple, Self
1818
)
1919
from typing_extensions import (
2020
TypedDict, Unpack, NotRequired
2121
)
2222

2323

24-
from amaranth import Const
24+
from amaranth import Const, ClockDomain, Fragment
2525
from amaranth.lib import wiring, io, meta
2626
from amaranth.lib.wiring import In, Out
2727
from 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]
7676
class 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+
97102
def 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):
474485
class 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

481493
def _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)

chipflow_lib/steps/silicon.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def elaborate(self, platform: SiliconPlatform):
4848
for n, t in top.items():
4949
setattr(m.submodules, n, t)
5050

51-
for component, iface in platform.pinlock.port_map.items():
51+
for component, iface in platform.pinlock.port_map.ports.items():
5252
for iface_name, member, in iface.items():
5353
for name, port in member.items():
5454
iface = getattr(top[component], iface_name)
@@ -127,6 +127,11 @@ def submit(self, rtlil_path, args):
127127
raise ChipFlowError(
128128
"Environment variable `CHIPFLOW_API_KEY` is empty."
129129
)
130+
self._chipflow_api_origin = os.environ.get("CHIPFLOW_API_ORIGIN", "https://build.chipflow.org")
131+
132+
assert self._chipflow_api_key
133+
assert self._chipflow_api_origin
134+
130135
with Halo(text="Submitting...", spinner="dots") as sp:
131136
fh = None
132137
submission_name = self.determine_submission_name()
@@ -176,18 +181,17 @@ def network_err(e):
176181
fh.close()
177182
exit(1)
178183

179-
sp.info(f"> Submitting {submission_name} for project {self.project_name} to ChipFlow Cloud {'('+os.environ.get('CHIPFLOW_API_ORIGIN')+')' if 'CHIPFLOW_API_ORIGIN' in os.environ else ''}")
184+
sp.info(f"> Submitting {submission_name} for project {self.project_name} to ChipFlow Cloud {self._chipflow_api_origin}")
180185
sp.start("Sending design to ChipFlow Cloud")
181186

182-
chipflow_api_origin = os.environ.get("CHIPFLOW_API_ORIGIN", "https://build.chipflow.org")
183-
build_submit_url = f"{chipflow_api_origin}/build/submit"
187+
build_submit_url = f"{self._chipflow_api_origin}/build/submit"
184188

185189
try:
186190
resp = requests.post(
187191
build_submit_url,
188192
# TODO: This needs to be reworked to accept only one key, auth accepts user and pass
189193
# TODO: but we want to submit a single key
190-
auth=(None, self._chipflow_api_key),
194+
auth=("", self._chipflow_api_key),
191195
data=data,
192196
files={
193197
"rtlil": open(rtlil_path, "rb"),
@@ -207,14 +211,14 @@ def network_err(e):
207211
try:
208212
resp_data = resp.json()
209213
except ValueError:
210-
resp_data = resp.text
214+
resp_data = {'message': resp.text}
211215

212216
# Handle response based on status code
213217
if resp.status_code == 200:
214218
logger.debug(f"Submitted design: {resp_data}")
215-
self._build_url = f"{chipflow_api_origin}/build/{resp_data['build_id']}"
216-
self._build_status_url = f"{chipflow_api_origin}/build/{resp_data['build_id']}/status"
217-
self._log_stream_url = f"{chipflow_api_origin}/build/{resp_data['build_id']}/logs?follow=true"
219+
self._build_url = f"{self._chipflow_api_origin}/build/{resp_data['build_id']}"
220+
self._build_status_url = f"{self._chipflow_api_origin}/build/{resp_data['build_id']}/status"
221+
self._log_stream_url = f"{self._chipflow_api_origin}/build/{resp_data['build_id']}/logs?follow=true"
218222

219223
sp.succeed("✅ Design submitted successfully! Build URL: {self._build_url}")
220224

@@ -251,13 +255,14 @@ def network_err(e):
251255
def _long_poll_stream(self, sp, network_err):
252256
steps = self._last_log_steps
253257
stream_event_counter = 0
258+
assert self._chipflow_api_key
254259
# after 4 errors, return to _stream_logs loop and query the build status again
255260
while (stream_event_counter < 4):
256261
sp.text = "Build running... " + ' -> '.join(steps)
257262
try:
258263
log_resp = requests.get(
259264
self._log_stream_url,
260-
auth=(None, self._chipflow_api_key),
265+
auth=("", self._chipflow_api_key),
261266
stream=True,
262267
timeout=(2.0, 60.0) # fail if connect takes >2s, long poll for 60s at a time
263268
)
@@ -284,18 +289,19 @@ def _long_poll_stream(self, sp, network_err):
284289
logger.debug(f"Failed to stream logs: {log_resp.text}")
285290
sp.text = "💥 Failed streaming build logs. Trying again!"
286291
break
287-
except requests.ConnectTimeout:
292+
except requests.ConnectionError as e:
293+
if type(e.__context__) is urllib3.exceptions.ReadTimeoutError:
294+
continue #just timed out, continue long poll
288295
sp.text = "💥 Failed connecting to ChipFlow Cloud."
289296
logger.debug(f"Error while streaming logs: {e}")
290297
break
291298
except (requests.RequestException, requests.exceptions.ReadTimeout) as e:
299+
if type(e.__context__) is urllib3.exceptions.ReadTimeoutError:
300+
continue #just timed out, continue long poll
292301
sp.text = "💥 Failed streaming build logs. Trying again!"
293302
logger.debug(f"Error while streaming logs: {e}")
294303
stream_event_counter +=1
295304
continue
296-
except requests.ConnectionError as e:
297-
if type(e.__context__) is urllib3.exceptions.ReadTimeoutError:
298-
continue #just timed out, continue long poll
299305

300306
# save steps so we coninue where we left off if we manage to reconnect
301307
self._last_log_steps = steps
@@ -309,13 +315,14 @@ def _stream_logs(self, sp, network_err):
309315
build_status = "pending"
310316
stream_event_counter = 0
311317
self._last_log_steps = []
318+
assert self._chipflow_api_key is not None
312319
while fail_counter < 10 and stream_event_counter < 10:
313320
sp.text = f"Waiting for build to run... {build_status}"
314321
time.sleep(timeout) # Wait before polling
315322
try:
316323
status_resp = requests.get(
317324
self._build_status_url,
318-
auth=(None, self._chipflow_api_key),
325+
auth=("", self._chipflow_api_key),
319326
timeout=timeout
320327
)
321328
except (requests.ConnectTimeout, requests.ConnectionError, requests.ConnectTimeout) as e:

0 commit comments

Comments
 (0)