From 9e2ec8688f421a2c1bc2ffff19a317e117a74b0d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:02:33 -0400 Subject: [PATCH 1/2] Carry each tile's module parameters in the capability contract Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- dau_core/capability_contract.py | 10 +++++++++- dau_core/tests/test_capability_contract.py | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/dau_core/capability_contract.py b/dau_core/capability_contract.py index 10d6412..99c53fb 100644 --- a/dau_core/capability_contract.py +++ b/dau_core/capability_contract.py @@ -47,7 +47,9 @@ def opcode_bitmap(opcodes: tuple[OperationCode, ...]) -> int: class TileCapability(CheckedModel): """One operator tile instance as the contract describes it: registry - identity, stream contract, HDL opcode truth, and instance config.""" + identity, stream contract, HDL opcode truth, instance config, and the + module parameters it was built with (each declared parameter, at its + override or its default).""" core: str module: str @@ -58,6 +60,7 @@ class TileCapability(CheckedModel): unit_kind: UnitKind | None = None row_capacity: int | None = None config: tuple[tuple[str, str], ...] = () + params: tuple[tuple[str, int], ...] = () @property def host_opcodes(self) -> tuple[OperationCode, ...]: @@ -79,6 +82,7 @@ def to_mapping(self) -> dict[str, Any]: "unit_kind": self.unit_kind.value if self.unit_kind is not None else None, "row_capacity": self.row_capacity, "config": dict(self.config), + "params": dict(self.params), } @classmethod @@ -93,6 +97,9 @@ def from_mapping(cls, mapping: Mapping[str, Any]) -> TileCapability: unit_kind=UnitKind(mapping["unit_kind"]) if mapping.get("unit_kind") else None, row_capacity=mapping.get("row_capacity"), config=tuple(sorted((str(key), str(value)) for key, value in dict(mapping.get("config", {})).items())), + # absent in manifests written before the fact was advertised; a + # reader that needs a parameter treats it as unknown + params=tuple(sorted((str(key), int(value)) for key, value in dict(mapping.get("params", {})).items())), ) @@ -247,6 +254,7 @@ def tile_capability(tile: TileSpec | str) -> TileCapability: unit_kind=definition.unit_kind, row_capacity=definition.row_capacity, config=tuple(sorted((str(key), str(value)) for key, value in spec.config.items())), + params=tuple(sorted((name, spec.params.get(name, parameter.default)) for name, parameter in definition.parameters.items())), ) diff --git a/dau_core/tests/test_capability_contract.py b/dau_core/tests/test_capability_contract.py index 4f84739..5c26f07 100644 --- a/dau_core/tests/test_capability_contract.py +++ b/dau_core/tests/test_capability_contract.py @@ -233,3 +233,25 @@ def test_reconfigurable_is_a_manifest_fact() -> None: legacy = baked.to_mapping() del legacy["reconfigurable"] assert CapabilityContract.from_mapping(legacy).reconfigurable is False + + +def test_a_tile_capability_carries_the_parameters_it_was_built_with() -> None: + from dau_core.operators import DispatchMode + + dispatcher = TileSpec(core="key-mask-dispatcher", params={"NUM_PARTITIONS": 4, "MODE": int(DispatchMode.EXACT_BRANCH)}) + lane = LaneSpec(tile=TileSpec(core="field-sum-aggregation", config={"cfg_field_mask": "4'b0010"})) + contract = capability_contract(ScanCompositionSpec(name="t", lanes=(lane,) * 4, partitioner=dispatcher)) + + assert contract.partitioner is not None + params = dict(contract.partitioner.params) + # overrides as given, every other declared parameter at its default + assert params["MODE"] == DispatchMode.EXACT_BRANCH + assert params["NUM_PARTITIONS"] == 4 + assert params["IN_WIDTH"] == 64 + assert set(params) == set(dispatcher.definition().parameters) + assert CapabilityContract.from_mapping(json.loads(json.dumps(contract.to_mapping()))) == contract + + # a manifest written before the parameters were advertised reads as unknown + mapping = contract.to_mapping() + del mapping["partitioner"]["params"] + assert CapabilityContract.from_mapping(mapping).partitioner.params == () From 6f8ff2acf510feba7a3f861c22c239d9fb26eb34 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:14:57 -0400 Subject: [PATCH 2/2] Narrow the restored partitioner in the contract params test Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- dau_core/tests/test_capability_contract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dau_core/tests/test_capability_contract.py b/dau_core/tests/test_capability_contract.py index 5c26f07..7a57706 100644 --- a/dau_core/tests/test_capability_contract.py +++ b/dau_core/tests/test_capability_contract.py @@ -254,4 +254,6 @@ def test_a_tile_capability_carries_the_parameters_it_was_built_with() -> None: # a manifest written before the parameters were advertised reads as unknown mapping = contract.to_mapping() del mapping["partitioner"]["params"] - assert CapabilityContract.from_mapping(mapping).partitioner.params == () + restored = CapabilityContract.from_mapping(mapping).partitioner + assert restored is not None + assert restored.params == ()