use crate::model::Layer;
use crate::passes::{Hints, OptimizedModel};
use crate::tune::RequantPrecision;
#[derive(Debug, Clone, Copy, Default)]
pub struct Estimate {
pub dsp: u32,
pub lut: u32,
pub bram_bytes: u32,
pub cycles: u64,
}
impl Estimate {
pub fn summary(&self) -> String {
format!(
"DSP={} LUT≈{} BRAM={}B cycles={}",
self.dsp, self.lut, self.bram_bytes, self.cycles,
)
}
}
pub fn estimate(opt: &OptimizedModel) -> Estimate {
let mut est = Estimate::default();
if opt.tune.arena_plan {
let scratch = opt.model.input_len.max(
opt.model
.layers
.iter()
.map(|l| l.out_len())
.max()
.unwrap_or(0),
);
est.bram_bytes += 2 * scratch as u32;
} else {
est.bram_bytes += opt.model.input_len as u32;
for l in &opt.model.layers {
est.bram_bytes += l.out_len() as u32;
}
}
for (i, layer) in opt.model.layers.iter().enumerate() {
let h = &opt.hints[i];
if h.elided {
continue;
}
match layer {
Layer::Conv2d {
h_in,
w_in,
c_in,
c_out,
kh,
kw,
pad_h,
pad_w,
stride_h,
stride_w,
weight_bits,
bias,
..
} => {
let h_out = (h_in + 2 * pad_h - kh) / stride_h + 1;
let w_out = (w_in + 2 * pad_w - kw) / stride_w + 1;
let logical_weights = (c_out * kh * kw * c_in) as u64;
conv_dense_resources(
&mut est,
logical_weights,
*c_out,
*weight_bits,
bias.is_some(),
h,
opt,
);
let p = h.parallelism.max(1) as u64;
let p_ic = h.ic_parallelism.max(1) as u64;
let inner_per_block = (kh * kw * c_in) as u64 / p_ic + 1;
let pixel_blocks = (h_out * w_out * (*c_out) / p as usize) as u64;
let epilogue_per_block = 3 + p;
est.cycles += pixel_blocks * (inner_per_block + epilogue_per_block);
if !h.ternary_fast_path {
est.dsp += (p - 1) as u32;
}
}
Layer::Dense {
in_features,
out_features,
weight_bits,
bias,
..
} => {
let logical_weights = (in_features * out_features) as u64;
conv_dense_resources(
&mut est,
logical_weights,
*out_features,
*weight_bits,
bias.is_some(),
h,
opt,
);
let inner = *in_features as u64 + 1;
est.cycles += (*out_features as u64) * (inner + 4);
}
Layer::Relu { len, .. } => {
est.cycles += 3 * *len as u64;
est.lut += 16; }
Layer::MaxPool2d {
h_in,
w_in,
c,
kh,
kw,
stride_h,
stride_w,
..
} => {
let h_out = (h_in - kh) / stride_h + 1;
let w_out = (w_in - kw) / stride_w + 1;
let cells = (h_out * w_out * c * kh * kw) as u64;
est.cycles += 3 * cells + 2 * (h_out * w_out * c) as u64;
est.lut += 32;
}
Layer::Argmax { len, .. } => {
est.cycles += 3 * *len as u64 + 4;
est.lut += 32;
}
}
}
est
}
fn conv_dense_resources(
est: &mut Estimate,
logical_weights: u64,
c_out: usize,
weight_bits: u8,
has_bias: bool,
h: &Hints,
opt: &OptimizedModel,
) {
if !h.ternary_fast_path {
est.dsp += 1;
}
est.dsp += 1;
let mut lut: u32 = 64 + 64;
if !h.fast_mac {
lut += 32;
}
lut += match opt.tune.requant_precision {
RequantPrecision::Q0_31 => 80,
RequantPrecision::Q0_15 => 50,
};
if h.ternary_fast_path {
lut = lut.saturating_sub(48);
}
est.lut += lut;
let weight_bytes = (logical_weights * weight_bits as u64).div_ceil(8) as u32;
est.bram_bytes += weight_bytes;
if has_bias {
est.bram_bytes += (c_out * 4) as u32;
}
if h.shared_requant.is_none() {
est.bram_bytes += (c_out * 5) as u32;
}
let _ = c_out;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::tinyconv_mnist_from_cortexm;
use crate::passes::optimize;
use crate::tune::{OptTarget, Tune};
#[test]
fn presets_move_resources_in_expected_directions() {
let model = tinyconv_mnist_from_cortexm();
let lat = estimate(&optimize(&model, &Tune::for_target(OptTarget::Latency)));
let sz = estimate(&optimize(&model, &Tune::for_target(OptTarget::Size)));
let prec = estimate(&optimize(&model, &Tune::for_target(OptTarget::Precision)));
assert!(
lat.cycles < prec.cycles,
"Latency cycles {} should be < Precision cycles {}",
lat.cycles,
prec.cycles
);
assert!(
sz.lut <= prec.lut,
"Size LUT {} > Precision LUT {}",
sz.lut,
prec.lut
);
assert!(prec.lut > sz.lut);
assert!(
lat.dsp > prec.dsp,
"Latency DSP {} should be > Precision DSP {}",
lat.dsp,
prec.dsp
);
}
#[test]
fn summary_renders() {
let model = tinyconv_mnist_from_cortexm();
let opt = optimize(&model, &Tune::default());
let s = estimate(&opt).summary();
assert!(s.contains("DSP="));
assert!(s.contains("BRAM="));
}
}