use super::coordinate::gcd;
use crate::definition::InterpolateBlueprint;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TileGeometry {
pub planes_per_cube: usize,
pub rows_per_plane: usize,
pub unit_cols: usize,
pub cols_per_unit: usize,
pub unit_channels: usize,
pub channel_block: usize,
}
impl TileGeometry {
pub fn from_blueprint(blueprint: InterpolateBlueprint, channels: usize, units: usize) -> Self {
let (unit_cols, unit_channels, channel_block) =
unit_split(channels, units, blueprint.channel_block);
Self {
planes_per_cube: blueprint.planes_per_cube,
rows_per_plane: blueprint.rows_per_plane,
unit_cols,
cols_per_unit: blueprint.cols_per_unit,
unit_channels,
channel_block,
}
}
pub fn rows_per_cube(&self) -> usize {
self.planes_per_cube * self.rows_per_plane
}
pub fn cols_per_cube(&self) -> usize {
self.unit_cols * self.cols_per_unit
}
pub fn channels_per_cube(&self) -> usize {
self.unit_channels * self.channel_block
}
}
fn unit_split(channels: usize, units: usize, block: Option<usize>) -> (usize, usize, usize) {
if let Some(block) = block {
assert!(
block > 0,
"TileGeometry: the channel block must be at least one element"
);
}
if units == 1 {
return (1, 1, block.unwrap_or_else(|| divisor_at_most(channels, 32)));
}
let channel_block = block.unwrap_or_else(|| divisor_at_most(channels, 4));
let unit_channels = gcd(units, channels.div_ceil(channel_block));
(units / unit_channels, unit_channels, channel_block)
}
fn divisor_at_most(n: usize, cap: usize) -> usize {
(1..=n.min(cap))
.rev()
.find(|d| n.is_multiple_of(*d))
.unwrap_or(1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::InputStage;
#[test]
fn the_unit_split_covers_the_plane() {
for units in [1, 8, 16, 32, 64] {
for channels in [1, 2, 3, 4, 8, 16, 32] {
let (unit_cols, unit_channels, _) = unit_split(channels, units, None);
assert_eq!(
unit_cols * unit_channels,
units,
"c={channels} over {units} units"
);
}
}
}
#[test]
fn a_plane_spreads_over_channels_before_columns() {
let (unit_cols, unit_channels, channel_block) = unit_split(3, 32, None);
assert_eq!((unit_cols, unit_channels, channel_block), (32, 1, 3));
let (unit_cols, unit_channels, channel_block) = unit_split(16, 32, None);
assert_eq!((unit_cols, unit_channels, channel_block), (8, 4, 4));
}
#[test]
fn a_plane_of_one_unit_covers_every_channel() {
for channels in [2, 3, 16] {
let blueprint = InterpolateBlueprint::new(InputStage::InPlace, 4, 8, 8);
let geometry = TileGeometry::from_blueprint(blueprint, channels, 1);
assert_eq!(geometry.unit_cols, 1);
assert_eq!(geometry.unit_channels, 1);
assert_eq!(geometry.channels_per_cube(), channels);
}
}
#[test]
fn a_stated_channel_block_solves_the_split_around_it() {
for (channels, block, expected) in [(16, 1, 16), (16, 2, 8), (16, 8, 2), (3, 1, 1)] {
let (unit_cols, unit_channels, channel_block) = unit_split(channels, 32, Some(block));
assert_eq!(channel_block, block, "c={channels} block={block}");
assert_eq!(unit_channels, expected, "c={channels} block={block}");
assert_eq!(unit_cols * unit_channels, 32, "c={channels} block={block}");
}
}
#[test]
fn a_channel_block_may_overhang_the_axis() {
assert_eq!(unit_split(3, 32, Some(4)), (32, 1, 4));
assert_eq!(unit_split(3, 32, Some(3)), (32, 1, 3));
}
#[test]
fn a_partly_covered_axis_still_splits_over_whole_blocks() {
let (unit_cols, unit_channels, block) = unit_split(6, 32, Some(4));
assert_eq!((unit_channels, block), (2, 4));
assert_eq!(unit_cols * unit_channels, 32);
}
#[test]
fn the_blueprint_states_every_tuning_choice() {
let blueprint = InterpolateBlueprint::new(InputStage::InPlace, 2, 4, 8);
let geometry = TileGeometry::from_blueprint(blueprint, 3, 32);
assert_eq!(geometry.planes_per_cube, 2);
assert_eq!(geometry.rows_per_plane, 4);
assert_eq!(geometry.cols_per_unit, 8);
assert_eq!(geometry.rows_per_cube(), 8);
}
}