test_composition

Attributes

_BAR

_PART

_CLOCK_NS

_FRONT_UNPACK_CONFIG

Functions

_priced(spec)

_tile_lut(name) → int

A tile's LUTs at the coordinate above.

_partition(lane, lanes) → dau_core.composition.TileSpec

bar_noc_spec([lanes]) → dau_core.composition.ScanCompositionSpec

test_spec_validates_lane_tiles() → None

test_core_names_cover_infrastructure_and_lanes() → None

test_estimated_resources_scale_with_lanes() → None

test_single_lane_filterless_scan_is_valid() → None

_range_partitioner(lanes) → dau_core.composition.TileSpec

sorted_scan_spec([lanes]) → dau_core.composition.ScanCompositionSpec

test_shared_partitioner_replaces_broadcast() → None

test_shared_partitioner_rejects_per_lane_partitions() → None

test_shared_partitioner_must_be_operator() → None

test_sorted_scan_resources_use_partitioner_envelope() → None

q6_scan_spec([lanes]) → dau_core.composition.ScanCompositionSpec

The q6 filter+reduce lane: key-partitioned fan-out, a chain of two

test_chain_lanes_validate_and_close_core_names() → None

test_chain_stages_must_be_operators() → None

test_chain_stages_need_no_status_counter() → None

test_chain_resources_are_counted_per_lane() → None

test_tile_params_validate_declared_names_and_positive_values() → None

test_a_design_may_pick_an_element_type_by_name() → None

What the migration is FOR: a design says what its fields are, not how

test_a_type_and_its_width_are_the_same_design() → None

int32 IS 32, so the two spellings are one cache key rather than two --

test_minimum_constraint_mirrors_the_rolling_window_guard() → None

test_a_declared_zero_minimum_admits_zero() → None

test_maximum_constraint_mirrors_the_bucket_bits_guard() → None

test_choices_and_power_of_two_constraints_mirror_the_elaboration_guards() → None

test_load_phase_shape_rules() → None

test_param_override_is_validated_on_the_lane_tile_and_partition_slots() → None

test_key_space_override_must_match_the_declared_multiple() → None

test_shared_partitioner_params_are_validated() → None

test_a_sized_tile_prices_at_its_size_not_its_default() → None

A parameter that changes the hardware has to change the price.

_front_unpack() → dau_core.composition.TileSpec

test_front_unpack_validates_and_reaches_core_names_and_resources() → None

test_front_unpack_rejects_a_non_unpack_tile() → None

test_front_unpack_less_composition_is_unchanged() → None

test_exactly_one_success_close_out_per_lane_is_enforced() → None

test_spec_canonical_round_trip_and_hash_stability() → None

test_spec_hash_is_config_order_insensitive_and_content_sensitive() → None

test_spec_hash_pinned_fixture() → None

The design-cache key must be stable across releases: ANY change to

test_spec_from_canonical_revalidates() → None

A tampered canonical form fails registry validation, never builds.

test_canonical_version_reads_the_prefix_a_recorded_hash_carries() → None

The prefix is what a reader has instead of the palette that wrote the

test_a_text_recorded_under_an_older_palette_is_refused_by_version_not_by_parse() → None

The defect this boundary closes, in one place.

test_spec_hash_stable_when_defaults_are_explicit() → None

test_handle_table_capacity_is_design_identity_and_inert_at_zero() → None

A handle table is HARDWARE — a table of flops and a resolve stage in

test_wide_data_width_composition_rules() → None

The canonical spec mirrors the walker's width rules: a wide feed

test_throughput_reproduces_the_measured_rate_ladder()

The rate model recomputes silicon: 64-bit two-beat = 1/2 row/cycle

test_throughput_packed_front_re_times_delivery()

test_the_wide_lane_shape_composes_and_rate_matches()

The wide lane: one lane of wide-row-stream stages consuming reader

test_wide_combinational_tile_pipeline_depth_bounds_are_enforced()

test_the_wide_lane_shape_rules_are_enforced()

test_wide_row_stream_cores_are_refused_in_the_partitioner_slot() → None

test_the_fused_chain_shape_is_declared_not_inferred() → None

A terminal-shaped tile mid-lane (the temporal hero's as-of join and

_temporal_hero_spec(*[, include_feature_gather]) → dau_core.composition.ScanCompositionSpec

test_temporal_hero_gathers_reconcile_every_stream_shape() → None

test_stream_shape_mismatch_names_both_cores_and_shapes() → None

test_stream_shape_mismatches_include_front_feed_and_partitioner() → None

test_selectivity_reconciles_the_model_with_q6_silicon() → None

The rate benches measured the q6 map stage at one row per SEVEN

test_throughput_breakdown_names_the_binding_stage() → None

_record_dispatcher([record_words]) → dau_core.composition.TileSpec

The dispatcher as it sits BEHIND a gearbox: its bus is one whole

_front_gearbox([record_words]) → dau_core.composition.TileSpec

test_front_gearbox_reaches_core_names_and_resources() → None

The gearbox must appear in core_names: that tuple is what stages the

test_a_geared_design_does_not_share_a_hash_with_an_ungeared_one() → None

The spec hash keys the design cache. If the gearbox were invisible to

test_the_gearbox_is_charged_as_the_front_delivery_it_is() → None

A GUARANTEED floor has to charge the stage that produces the rows.

test_a_wider_record_lowers_the_geared_delivery() → None

The record size is the DIVISOR of the reader's supply, so it has to

test_a_gearbox_record_mismatch_is_detected() → None

stream_shape_mismatches exists to find exactly this, and the front

test_front_gearbox_refuses_a_narrow_feed_and_a_rival_front() → None

_stream_job_spec([job, tile]) → dau_core.composition.ScanCompositionSpec

test_a_stream_job_composition_declares_its_own_infrastructure() → None

A stream job stages one tile chain through its job core instead of

test_a_stream_job_composition_refuses_scan_shaped_fields() → None

The stream job has no fan-out and no burst feed, so accepting a

test_the_scan_shell_still_requires_a_lane_counter() → None

The count_port waiver is the STREAM job's, not a general relaxation:

test_addr_width_is_32_or_64_only() → None

A job master of some intermediate width is not hardware anyone builds,

test_a_packed_front_can_feed_a_wide_lane() → None

The packed feed and the wide lane compose.

test_a_packed_wide_feed_rates_at_the_unpacker_not_at_one_row() → None

The packed feed's ceiling is the READER's packed-row supply, which is

test_shell_infrastructure_is_selected_by_role_not_by_name() → None

Composition IS the registry: the shell cores a composition places are

test_a_stream_job_must_name_a_stream_job_shell() → None

Module Contents

test_composition._BAR
test_composition._PART = 'xc7k325tffg900-2'
test_composition._CLOCK_NS = 8.0
test_composition._priced(spec: dau_core.composition.ScanCompositionSpec)
test_composition._tile_lut(name: str) → int

A tile’s LUTs at the coordinate above.

test_composition._partition(lane: int, lanes: int) → dau_core.composition.TileSpec
test_composition.bar_noc_spec(lanes: int = 4) → dau_core.composition.ScanCompositionSpec
test_composition.test_spec_validates_lane_tiles() → None
test_composition.test_core_names_cover_infrastructure_and_lanes() → None
test_composition.test_estimated_resources_scale_with_lanes() → None
test_composition.test_single_lane_filterless_scan_is_valid() → None
test_composition._range_partitioner(lanes: int) → dau_core.composition.TileSpec
test_composition.sorted_scan_spec(lanes: int = 4) → dau_core.composition.ScanCompositionSpec
test_composition.test_shared_partitioner_replaces_broadcast() → None
test_composition.test_shared_partitioner_rejects_per_lane_partitions() → None
test_composition.test_shared_partitioner_must_be_operator() → None
test_composition.test_sorted_scan_resources_use_partitioner_envelope() → None
test_composition.q6_scan_spec(lanes: int = 4) → dau_core.composition.ScanCompositionSpec

The q6 filter+reduce lane: key-partitioned fan-out, a chain of two row-predicate filters (the four-comparison WHERE split across two 3-term tiles) and the revenue map, closed by the field-sum tile.

test_composition.test_chain_lanes_validate_and_close_core_names() → None
test_composition.test_chain_stages_must_be_operators() → None
test_composition.test_chain_stages_need_no_status_counter() → None
test_composition.test_chain_resources_are_counted_per_lane() → None
test_composition.test_tile_params_validate_declared_names_and_positive_values() → None
test_composition.test_a_design_may_pick_an_element_type_by_name() → None

What the migration is FOR: a design says what its fields are, not how many bits they take.

test_composition.test_a_type_and_its_width_are_the_same_design() → None

int32 IS 32, so the two spellings are one cache key rather than two – a design cache that missed on spelling would rebuild an identical shell.

test_composition.test_minimum_constraint_mirrors_the_rolling_window_guard() → None
test_composition.test_a_declared_zero_minimum_admits_zero() → None
test_composition.test_maximum_constraint_mirrors_the_bucket_bits_guard() → None
test_composition.test_choices_and_power_of_two_constraints_mirror_the_elaboration_guards() → None
test_composition.test_load_phase_shape_rules() → None
test_composition.test_param_override_is_validated_on_the_lane_tile_and_partition_slots() → None
test_composition.test_key_space_override_must_match_the_declared_multiple() → None
test_composition.test_shared_partitioner_params_are_validated() → None
test_composition.test_a_sized_tile_prices_at_its_size_not_its_default() → None

A parameter that changes the hardware has to change the price.

This assertion used to be the OPPOSITE – that a 6M-key membership filter priced identically to a default 4096-key one – and it was true only because the tile carried a scalar envelope that answered every KEY_SPACE with the same numbers. The membership filter is a BITMAP, so its BRAM cost IS the key space: 1 BRAM36 at 4096, 192 at 6,000,000, which is 57% of the Artix’s 335 budgeted blocks. Every composition using the silicon-proven configuration was priced at 1/192 of the BRAM it actually places.

test_composition._FRONT_UNPACK_CONFIG
test_composition._front_unpack() → dau_core.composition.TileSpec
test_composition.test_front_unpack_validates_and_reaches_core_names_and_resources() → None
test_composition.test_front_unpack_rejects_a_non_unpack_tile() → None
test_composition.test_front_unpack_less_composition_is_unchanged() → None
test_composition.test_exactly_one_success_close_out_per_lane_is_enforced() → None
test_composition.test_spec_canonical_round_trip_and_hash_stability() → None
test_composition.test_spec_hash_is_config_order_insensitive_and_content_sensitive() → None
test_composition.test_spec_hash_pinned_fixture() → None

The design-cache key must be stable across releases: ANY change to the canonical form — field ordering, a new or changed spec field or default, hash construction — fails HERE, loudly, and must bump SPEC_CANONICAL_VERSION deliberately (defaults are included precisely so a changed default changes the key instead of colliding).

test_composition.test_spec_from_canonical_revalidates() → None

A tampered canonical form fails registry validation, never builds.

test_composition.test_canonical_version_reads_the_prefix_a_recorded_hash_carries() → None

The prefix is what a reader has instead of the palette that wrote the text beside it, so it has to be readable without one.

test_composition.test_a_text_recorded_under_an_older_palette_is_refused_by_version_not_by_parse() → None

The defect this boundary closes, in one place.

A build manifest written before the width-named tiles were renamed records a canonical text naming them. Reading it against today’s registry raises UnknownCoreError — an error about a missing tile, from a design that is built, flashed and answering. The version prefix beside the text is the fact that says the text predates this palette, and it says so without a parse.

test_composition.test_spec_hash_stable_when_defaults_are_explicit() → None
test_composition.test_handle_table_capacity_is_design_identity_and_inert_at_zero() → None

A handle table is HARDWARE — a table of flops and a resolve stage in front of the reader — so a design that carries one must not share a cache key with one that does not, and eight slots is not sixteen. Zero is the no-table default: a spec that never mentions it keeps the shape it has today and round-trips unchanged.

test_composition.test_wide_data_width_composition_rules() → None

The canonical spec mirrors the walker’s width rules: a wide feed needs a dispatcher (broadcast is 64-bit only), wide packed reads are a typed follow-up, and the tier set is closed.

test_composition.test_throughput_reproduces_the_measured_rate_ladder()

The rate model recomputes silicon: 64-bit two-beat = 1/2 row/cycle (~99 ms q6), the 512-bit dispatch feed = 1 (the serializer, 60.3 ms measured 2026-07-30), and MORE LANES DO NOT HELP past the dispatcher’s declared ingest — the motivation for the wide-sum lane / distributor.

test_composition.test_throughput_packed_front_re_times_delivery()
test_composition.test_the_wide_lane_shape_composes_and_rate_matches()

The wide lane: one lane of wide-row-stream stages consuming reader beats directly — SLOTS rows per cycle, no partitioner. The q6 shape at 512 rates 4 rows/cycle (the parallel-row target).

test_composition.test_wide_combinational_tile_pipeline_depth_bounds_are_enforced()
test_composition.test_the_wide_lane_shape_rules_are_enforced()
test_composition.test_wide_row_stream_cores_are_refused_in_the_partitioner_slot() → None
test_composition.test_the_fused_chain_shape_is_declared_not_inferred() → None

A terminal-shaped tile mid-lane (the temporal hero’s as-of join and grouped aggregator) composes only when the spec DECLARES the fused chain: the refusal still catches an aggregator dropped into a chain by accident, while the declaration says the caller accepts that mid-stage close-outs are drained and only their errors reach the lane status.

test_composition._temporal_hero_spec(*, include_feature_gather: bool = True) → dau_core.composition.ScanCompositionSpec
test_composition.test_temporal_hero_gathers_reconcile_every_stream_shape() → None
test_composition.test_stream_shape_mismatch_names_both_cores_and_shapes() → None
test_composition.test_stream_shape_mismatches_include_front_feed_and_partitioner() → None
test_composition.test_selectivity_reconciles_the_model_with_q6_silicon() → None

The rate benches measured the q6 map stage at one row per SEVEN cycles standalone, which made the no-selectivity model predict 1/7 for q6 — 3.4x below the 0.483 rows/cycle silicon actually ran. Both numbers are right: two predicates upstream pass ~2% of rows, so the map never binds the feed. Charging each stage against the rows that reach it restores agreement, and the worst case stays the default because selectivity is data-dependent.

test_composition.test_throughput_breakdown_names_the_binding_stage() → None
test_composition._record_dispatcher(record_words: int = 3) → dau_core.composition.TileSpec

The dispatcher as it sits BEHIND a gearbox: its bus is one whole record wide, so it takes RECORD_INPUT rather than a row framing.

test_composition._front_gearbox(record_words: int = 3) → dau_core.composition.TileSpec
test_composition.test_front_gearbox_reaches_core_names_and_resources() → None

The gearbox must appear in core_names: that tuple is what stages the HDL a build compiles, so a gearbox missing from it is instantiated by the generated top and never handed to the toolchain — the design black-boxes at synthesis.

test_composition.test_a_geared_design_does_not_share_a_hash_with_an_ungeared_one() → None

The spec hash keys the design cache. If the gearbox were invisible to the canonical form, a cached ungeared bitstream would be handed back for a geared design and silently run the wrong front.

test_composition.test_the_gearbox_is_charged_as_the_front_delivery_it_is() → None

A GUARANTEED floor has to charge the stage that produces the rows.

The gearbox was validated by five walkers and priced by none: absent from this function, a geared composition took the quad-row delivery of data_width/128 – 24-byte records charged as 16-byte ones – and the gearbox’s own one-record-per-cycle ceiling was never applied at all. At data_width=512 that reports 4 rows/cycle for a front that sustains 1.

test_composition.test_a_wider_record_lowers_the_geared_delivery() → None

The record size is the DIVISOR of the reader’s supply, so it has to move the answer – a delivery term that ignores it is the shape that charged 24-byte records at the 16-byte quad-row rate.

test_composition.test_a_gearbox_record_mismatch_is_detected() → None

stream_shape_mismatches exists to find exactly this, and the front stage it never looked at was the gearbox: a 3-word gearbox feeding a 4-word dispatcher validated clean and the detector returned ().

test_composition.test_front_gearbox_refuses_a_narrow_feed_and_a_rival_front() → None
test_composition._stream_job_spec(job: str = 'bram-stream-job', tile: str = 'bar-aggregation') → dau_core.composition.ScanCompositionSpec
test_composition.test_a_stream_job_composition_declares_its_own_infrastructure() → None

A stream job stages one tile chain through its job core instead of reading bursts and fanning out. Until it could be declared, its cores reached the build as a hand-copied list — so nothing checked that the list still matched what the top instantiates.

test_composition.test_a_stream_job_composition_refuses_scan_shaped_fields() → None

The stream job has no fan-out and no burst feed, so accepting a partitioner or a front stage would let a spec describe hardware the shell cannot build.

test_composition.test_the_scan_shell_still_requires_a_lane_counter() → None

The count_port waiver is the STREAM job’s, not a general relaxation: the scan shell’s record writer sizes its write-out from that port.

test_composition.test_addr_width_is_32_or_64_only() → None

A job master of some intermediate width is not hardware anyone builds, and permitting it would mean carrying padding logic through every register slice for a shape with nothing behind it: the AXI-Lite read path zero-extends cleanly at exactly 32 and 64 and needs an explicit pad at anything else.

test_composition.test_a_packed_front_can_feed_a_wide_lane() → None

The packed feed and the wide lane compose.

Each is silicon-proven alone and they were mutually exclusive: the wide lane read whole quad rows straight from the reader, so it burned 16 bytes of DDR per row, and the next SLOTS doubling would have demanded 19.2 GB/s from a 12.8 GB/s DDR3. Packed, the same lane demands 9.6.

Two widths are in play and they are NOT the same number. data_width is the READ width – SLOTS packed 64-bit rows per beat – while the lane downstream of the unpacker sees SLOTS whole 128-bit quad rows. Note the consequence: an unpacked SLOTS=8 lane would need data_width=1024, which is not a legal tier, so packing is what puts eight slots in reach at all.

test_composition.test_a_packed_wide_feed_rates_at_the_unpacker_not_at_one_row() → None

The packed feed’s ceiling is the READER’s packed-row supply, which is data_width/64, not a flat one row per cycle.

The flat cap read the packed path as “one 8-byte row per 64-bit beat”, true only while data_width was 64. A packed WIDE feed hands the unpacker data_width/64 packed rows per beat, and capping at 1 under-reported the silicon-measured eight-slot lane by 8x – the design would have been priced as no faster than the build it replaced.

test_composition.test_shell_infrastructure_is_selected_by_role_not_by_name() → None

Composition IS the registry: the shell cores a composition places are the ones declaring each infrastructure role, exactly one per role, and composition.py names none of them. The order the scan roles are placed in is pinned because core_names feeds the canonical hash.

test_composition.test_a_stream_job_must_name_a_stream_job_shell() → None