Predicting

One module

Once the add_rm method of a HwModule is defined, we can use the predict method to estimate the resources consumed by a particular configuration. For example, in the vector multiplier example:

top = elaborate(VecMult, {"dwid": 64, "vlen": 4096}, name="vec_mult")
top.add_rm(platform)
top.resource_model.predict(top)
# {'lut': 1370, 'ff': 598, 'dsp': 4, 'bram': 4, 'uram': 0, 'srl': 0}

predict takes the component, not a feature vector — see why. It returns that module’s own counters, never its children’s.

A hierarchy

To estimate the total resource used by the HwModule and its sub-modules, use the compose method. The compose method returns a structure with total resources, the confidence level, and other parameters:

est = compose(top)

est.total        # {'lut': …, 'ff': …, 'dsp': …, 'bram': …}
est.level        # the WEAKEST confidence that fed it
est.weakest()    # which modules sit at it — what you would recalibrate first
est.per_module   # [(path, cls_name, counters, Confidence), ...]

The composition rule

The compose method is based on recursively applying predict:

predict(comp)  =  comp's OWN model  +  Σ predict(child)

A leaf’s own cost is its whole cost. A composite’s own cost is what it adds beyond its children — the m_axi adapters, the inter-task FIFOs, the AXI-Lite control block, the DATAFLOW shell.

That is not a third term bolted onto a per-module sum: it is the same rule one level up, and it is exactly what a synthesis report measures as top row − Σ task rows (Composite kernels). Definition and measurement coincide with nothing left over, which is what makes the model checkable against a whole-design run.

Nothing is passed down. Each model reads its own features off the component it is attached to, because elaboration already resolved every child’s parameters — so a child’s features cannot drift from the design that was synthesized.

The interface term

A composite’s own cost comes out by subtraction only because Vitis declines to itemize it. It is in one-to-one correspondence with the design’s interface graph:

unreported RTL comes from
gmem<n>_m_axi one per m_axi boundary port
fifo_w<W>_d<D>_S one per internal task-to-task channel
control_s_axi the ap_ctrl / AXI-Lite block
entry_proc, regslice_both, sparsemux_* the DATAFLOW shell

So InterfaceResourceModel keys on a boundary_signature — port kinds and widths, channel widths — rather than on parameters. The evidence is two-sided: across 24 points varying the compute parameters the term never moved, and it did move when the memory word width changed, identically for both realizations at each width.

The term can be negative when HLS shares logic across a module boundary. Nothing clamps it. A negative own-cost is information rather than an error — it is exactly the cross-block surprise that whole-design synthesis exists to catch, and it is invisible if modules are only ever measured standalone. VecMult shows a small one: lut: -2.

Confidence

Every model returns a Confidence beside its counters, and a composed estimate reports the weakest one:

level means
EXACT the form reproduces every calibration point with zero residual — a checked claim
INTERPOLATED the query lies inside the region the model was fit over
EXTRAPOLATED outside it — and what you cross on the way out is usually a regime boundary
UNCALIBRATED no fit backs this number

A VitisResourceModel illustrates why the weakest link is the right rule. Two of its counters are zero-parameter rules that reproduce every measurement; two are regressions. The composed verdict is INTERPOLATED, not EXACT:

total   {'lut': 1370, 'ff': 597, 'dsp': 4, 'bram': 4}
level   INTERPOLATED
weakest [('vec_mult', 'VecMult')]

An estimate reporting EXACT while half of it is a fit would be the most misleading thing this layer could do.

The facts carry the reasons, not just the level — for that module: “lut: form reproduces all 15 calibration points exactly (4 free parameters); ff: inside the calibrated region; worst residual on the corpus was 1.3%”.

EXTRAPOLATED deserves particular attention on this axis. Leaving the measured region usually means crossing a binding threshold — DSP-versus-LUT inference, block-RAM-versus-LUTRAM partitioning — and those move several counters at once rather than degrading smoothly.

What a missing model does

A module with no model contributes zero and reports UNCALIBRATED, naming itself. It is never silently skipped:

Confidence.uncalibrated("Foo has no resource model; its cost is missing from this estimate, not zero")

Under-counting is the one direction an estimate must not err, because it turns “does not fit” into “fits”. The same rule covers a counter a model does not predict: reported by name, never defaulted to zero.

Checking an estimate against reality

The composed total is comparable to a whole-design synthesis report directly — that is the point of the interface term being defined as the report measures it. See Composite kernels for getting the measured side, and Fitting for what to compare and what “close enough” should mean.

Next

  • Fitting — where the coefficients come from, and how to validate them.