Resource measurements
Timing is fit — a model form whose coefficients are recovered from a sweep. Resources are measured: the synthesis report says 32 DSPs and that is what it is. What calibration adds is keeping the measurement, keyed so a later design can reuse it instead of paying for another synthesis.
This page covers storing and sweeping. For how a report is read and how a composite’s total is decomposed into modules and interface logic, see Resource Analysis Tools — in particular Composite kernels, which explains the attribution this page files.
The build step
InspectSynthStep is the DAG rung after csynth. It attributes the report
(report_from_solution), writes it as JSON, and files the per-module
records into the platform’s module store:
from waveflow.build.resource_steps import InspectSynthStep
dag.add(CSynthStep(name="csynth"))
dag.add(InspectSynthStep(
name="resources", comp_class=FirBlock, top_name="fir_block",
elaborate_params=("mem_dwidth", "ntap", "samp_w", "samp_i", "unroll_lane"),
params={"mem_dwidth": 32, "ntap": 32, "samp_w": 16, "samp_i": 2, "unroll_lane": False}))
elaborate_params names which of the step’s params are elaboration parameters — the rest (a
live_output flag, say) are the step’s own business and must not reach elaborate.
Everything expensive already happened: the synthesis is the cost, and this is reading its output. That
is why it is worth running on every csynth rather than only when someone remembers to calibrate.
The corollary is that it must never fail a build that synthesized correctly — a report it cannot
attribute is a real error and raises, but a build with no platform selected simply
writes results/resources.json and says so.
Recording what it cost
The producing step publishes its own wall-clock as an artifact:
produces = {"report_dir": Path("fir_block_proj/solution1"), "synth_seconds": None}
InspectSynthStep consumes it and stamps it into each record’s cost_seconds, split evenly across the
modules — the synthesis was one indivisible run, and pretending to know each module’s share of it would
be inventing data.
Cost is recorded, never modelled. A history of real runs answers “what would recalibrating here cost?” better than any estimate could, and it is unrecoverable if not captured at the moment it was spent.
cost_secondsis machine-local while the measurement is not. A record published from one machine carries a duration that means nothing on another, so treat it as provenance (“this took four minutes to produce”) rather than as a portable prediction.
Sweeping a grid
examples/fir_block/fir_block_sweep.py drives one csynth per design point:
python -m examples.fir_block.fir_block_sweep --dry-run # elaborate + codegen only, no Vitis
python -m examples.fir_block.fir_block_sweep # the full sweep
python -m examples.fir_block.fir_block_sweep --resume # continue an interrupted one
Three habits worth copying into any sweep of your own:
- Pre-flight the whole grid without the toolchain first.
--dry-runruns every point through codegen in about a second. Learning that a parameter combination does not generate is worth one second, not two hours. - Write incrementally and support resume. Hours of synthesis should not be lost to one crash near the end.
- Record failures as failures. A sweep that quietly covered 19 of 24 points while reporting 24 leaves a hole in the fitted region — exactly where confidence would later claim interpolation.
A sweep writes the work tier (
calib/work/<name>), never the tracked library. Give it its own platform name: reusing a shipped platform’s name makesPlatform.resolvefind the packaged directory through its fallbacks and write there, and onlypublish_calibmay do that. See the calibration workflow.
What a sweep buys
The reference sweep over ntap ∈ {8,16,32} × samp_w ∈ {8,12,16,24} × {serial, unroll} — 24 points,
about 20 minutes — produced 96 module measurements over only 30 distinct configurations:
| module | distinct keys across the grid |
|---|---|
FirCompute |
24 — moves with every knob |
FirCmdRx |
4 — sees only samp_w |
MemRStream |
1 — sees neither ntap nor samp_w |
MemWStream |
1 |
The two memory modules were characterized once and served all 24 design points. That is the structural keying paying off in syntheses rather than in argument, and it is the mechanism by which a large design space costs far fewer runs than it has points.
See also
- Module keys and the record store — how a measurement is addressed, verified, filed.
- Resource Analysis Tools — reading the report and decomposing a composite.
- Platforms — resource counts are part- and clock-specific, and keyed accordingly.