#![allow(clippy::multiple_inherent_impl)]
use core::ops::{Add, Mul, Not as _, Sub};
#[cfg(feature = "table_template")]
use crate::common::table::TableCtx as _;
use crate::{
ast::{
self, fmt_comment_liberty, BuilderScope, GroupComments, GroupFn, ParseScope,
ParsingBuilder, SimpleAttri,
},
common::{
f64_into_hash_ord_fn,
table::{DisplayTableLookUp, DisplayValues, TableLookUp2D},
},
expression::logic,
Ctx,
};
use itertools::izip;
use strum_macros::{Display, EnumString};
#[derive(
Debug, Clone, Copy, PartialEq, Display, EnumString, Default, Hash, Eq, PartialOrd, Ord
)]
#[derive(serde::Serialize, serde::Deserialize)]
pub enum TimingSenseType {
#[strum(serialize = "positive_unate")]
PositiveUnate,
#[strum(serialize = "negative_unate")]
NegativeUnate,
#[strum(serialize = "non_unate")]
#[default]
NonUnate,
}
impl TimingSenseType {
#[must_use]
#[inline]
pub fn compute_edge(&self, pin_edge: &logic::Edge) -> Option<logic::Edge> {
match self {
Self::PositiveUnate => Some(*pin_edge),
Self::NegativeUnate => Some(pin_edge.not()),
Self::NonUnate => None,
}
}
}
crate::ast::impl_self_builder!(TimingSenseType);
impl<C: Ctx> SimpleAttri<C> for TimingSenseType {
#[inline]
fn nom_parse<'a>(i: &'a str, scope: &mut ParseScope) -> ast::SimpleParseRes<'a, Self> {
ast::nom_parse_from_str::<C, _>(i, scope)
}
}
#[mut_set::derive::item(sort)]
#[derive(Debug, Clone)]
#[derive(liberty_macros::Group)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(bound = "C::Other: serde::Serialize + serde::de::DeserializeOwned")]
pub struct CellDegradation<C: Ctx> {
#[size = 8]
#[liberty(name)]
#[id(borrow = "&str", with_ref = false)]
pub name: String,
#[size = 32]
#[liberty(comments)]
comments: GroupComments,
#[size = 0]
#[liberty(extra_ctx)]
pub extra_ctx: C::Other,
#[size = 40]
#[liberty(attributes)]
pub attributes: ast::Attributes,
#[size = 24]
#[liberty(complex)]
pub index_1: Vec<f64>,
#[size = 24]
#[liberty(complex)]
pub values: Vec<f64>,
}
impl<C: Ctx> GroupFn<C> for CellDegradation<C> {}
#[derive(Debug, Clone, Default)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct TimingTableLookUp<C: Ctx> {
pub extra_ctx: C::Table,
pub name: String,
pub comments: String,
pub index_1: Vec<f64>,
pub index_2: Vec<f64>,
pub size1: usize,
pub size2: usize,
pub values: Vec<f64>,
pub lvf_index_1: Vec<f64>,
pub lvf_index_2: Vec<f64>,
pub lvf_values: Vec<LVFValue>,
}
#[expect(
clippy::similar_names,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
impl<C: Ctx> TimingTableLookUp<C> {
#[inline]
const fn find_pos(len: usize, pos: usize) -> Option<(usize, usize)> {
if len <= 1 {
None
} else {
Some(if pos == 0 {
(0, 1)
} else if pos == len {
(len - 2, len - 1)
} else {
(pos - 1, pos)
})
}
}
#[inline]
fn get_value(&self, ix: usize, iy: usize) -> f64 {
self.values[ix * self.index_2.len() + iy]
}
#[inline]
fn get_lvf_value(&self, ix: usize, iy: usize) -> LVFValue {
self.lvf_values[ix * self.index_2.len() + iy]
}
#[must_use]
#[inline]
#[expect(clippy::float_arithmetic)]
pub fn lookup(&self, idx1: &f64, idx2: &f64) -> Option<f64> {
let idx1_ = f64_into_hash_ord_fn(idx1);
let idx2_ = f64_into_hash_ord_fn(idx2);
match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
Ok(i1_) => {
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => Some(self.get_value(i1_, i_1)),
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q_1 = self.get_value(i1_, i_1);
let q_2 = self.get_value(i1_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
(q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
}),
}
}
Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
let x1_ = self.index_1[i1_];
let x2_ = self.index_1[i2_];
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => {
let q1_ = self.get_value(i1_, i_1);
let q2_ = self.get_value(i2_, i_1);
Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
}
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q11 = self.get_value(i1_, i_1);
let q12 = self.get_value(i1_, i_2);
let q21 = self.get_value(i2_, i_1);
let q22 = self.get_value(i2_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
(q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
}),
}
}),
}
}
#[must_use]
#[inline]
#[expect(clippy::float_arithmetic)]
pub fn lookup_lvf(&self, idx1: &f64, idx2: &f64) -> Option<LVFValue> {
let idx1_ = f64_into_hash_ord_fn(idx1);
let idx2_ = f64_into_hash_ord_fn(idx2);
match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
Ok(i1_) => {
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => Some(self.get_lvf_value(i1_, i_1)),
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q_1 = self.get_lvf_value(i1_, i_1);
let q_2 = self.get_lvf_value(i1_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
(q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
}),
}
}
Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
let x1_ = self.index_1[i1_];
let x2_ = self.index_1[i2_];
match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
Ok(i_1) => {
let q1_ = self.get_lvf_value(i1_, i_1);
let q2_ = self.get_lvf_value(i2_, i_1);
Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
}
Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
let q11 = self.get_lvf_value(i1_, i_1);
let q12 = self.get_lvf_value(i1_, i_2);
let q21 = self.get_lvf_value(i2_, i_1);
let q22 = self.get_lvf_value(i2_, i_2);
let x_1 = self.index_2[i_1];
let x_2 = self.index_2[i_2];
let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
(q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
}),
}
}),
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, Copy)]
pub struct LVFValue {
pub mean: f64,
pub std_dev: f64,
pub skewness: f64,
}
impl LVFValue {
#[inline]
#[must_use]
pub fn mul_add(self, a: f64, b: Self) -> Self {
Self {
mean: self.mean.mul_add(a, b.mean),
std_dev: self.std_dev.mul_add(a, b.std_dev),
skewness: self.skewness.mul_add(a, b.skewness),
}
}
}
impl PartialEq for LVFValue {
#[inline]
fn eq(&self, other: &Self) -> bool {
f64_into_hash_ord_fn(&self.mean) == f64_into_hash_ord_fn(&other.mean)
&& f64_into_hash_ord_fn(&self.std_dev) == f64_into_hash_ord_fn(&other.std_dev)
&& f64_into_hash_ord_fn(&self.skewness) == f64_into_hash_ord_fn(&other.skewness)
}
}
#[expect(clippy::float_arithmetic)]
impl Add for LVFValue {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
Self {
mean: self.mean + rhs.mean,
std_dev: self.std_dev + rhs.std_dev,
skewness: self.skewness + rhs.skewness,
}
}
}
#[expect(clippy::float_arithmetic)]
impl Sub for LVFValue {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
Self {
mean: self.mean - rhs.mean,
std_dev: self.std_dev - rhs.std_dev,
skewness: self.skewness - rhs.skewness,
}
}
}
#[expect(clippy::float_arithmetic)]
impl Mul<f64> for LVFValue {
type Output = Self;
#[inline]
fn mul(self, rhs: f64) -> Self::Output {
Self {
mean: self.mean * rhs,
std_dev: self.std_dev * rhs,
skewness: self.skewness * rhs,
}
}
}
impl<C: Ctx> ParsingBuilder<C> for Option<TimingTableLookUp<C>> {
type Builder = (
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
);
#[inline]
#[cfg_attr(not(feature = "table_template"), expect(unused_mut, unused_variables))]
#[expect(clippy::float_arithmetic)]
fn build(builder: Self::Builder, scope: &mut BuilderScope<C>) -> Self {
#[inline]
fn eq_index<C: Ctx>(
lhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
rhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
) -> bool {
lhs.index_1 == rhs.index_1 && lhs.index_2 == rhs.index_2
}
match builder {
(Some(_value), Some(_mean_shift), Some(_std_dev), Some(_skewness)) => {
let lvf_nomial_same_index = eq_index(&_value, &_mean_shift);
let valid_lvf_index =
eq_index(&_mean_shift, &_std_dev) && eq_index(&_std_dev, &_skewness);
let (lvf_values, comments) = if valid_lvf_index {
(
izip!(
_value.values.inner.iter(),
_mean_shift.values.inner,
_std_dev.values.inner,
_skewness.values.inner
)
.map(|(value, mean_shift, std_dev, skewness)| {
let mean = value + mean_shift;
LVFValue { mean, std_dev, skewness }
})
.collect(),
String::new(),
)
} else {
log::error!("LVF LUTs' index mismatch");
(Vec::new(), String::from("LVF LUTs' index mismatch"))
};
let mut extra_ctx = C::Table::default();
#[cfg(feature = "table_template")]
if let Some(template) = scope.lu_table_template.get(&_value.name) {
extra_ctx.set_lu_table_template(template);
}
Some(TimingTableLookUp {
extra_ctx,
name: _value.name,
comments,
index_1: _value.index_1,
index_2: _value.index_2,
size1: _value.values.size1,
size2: _value.values.size2,
values: _value.values.inner,
lvf_index_1: if lvf_nomial_same_index {
Vec::new()
} else {
_mean_shift.index_1
},
lvf_index_2: if lvf_nomial_same_index {
Vec::new()
} else {
_mean_shift.index_2
},
lvf_values,
})
}
(Some(_value), None, None, None) => {
let mut extra_ctx = C::Table::default();
#[cfg(feature = "table_template")]
if let Some(template) = scope.lu_table_template.get(&_value.name) {
extra_ctx.set_lu_table_template(template);
}
Some(TimingTableLookUp {
extra_ctx,
name: _value.name,
comments: String::new(),
index_1: _value.index_1,
index_2: _value.index_2,
size1: _value.values.size1,
size2: _value.values.size2,
values: _value.values.inner,
lvf_index_1: Vec::new(),
lvf_index_2: Vec::new(),
lvf_values: Vec::new(),
})
}
_ => None,
}
}
}
impl<C: Ctx> TimingTableLookUp<C> {
#[inline]
#[expect(clippy::float_arithmetic)]
pub(crate) fn fmt_liberty<T: core::fmt::Write, I: ast::Indentation>(
&self,
key: &str,
f: &mut ast::CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
fmt_comment_liberty(Some(&self.comments), f)?;
DisplayTableLookUp {
name: &self.name,
index_1: &self.index_1,
index_2: &self.index_2,
values: DisplayValues {
size1: self.size1,
inner: self.values.iter().copied(),
},
}
.fmt_self::<_, _, C>("", key, f)?;
if !self.lvf_values.is_empty() {
let mismatch_index = !self.lvf_index_1.is_empty();
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
size1: self.size1,
inner: izip!(self.values.iter(), self.lvf_values.iter())
.map(|(value, lvf)| lvf.mean - value),
},
}
.fmt_self::<_, _, C>("ocv_mean_shift_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
size1: self.size1,
inner: self.lvf_values.iter().map(|lvf| lvf.std_dev),
},
}
.fmt_self::<_, _, C>("ocv_std_dev_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
values: DisplayValues {
size1: self.size1,
inner: self.lvf_values.iter().map(|lvf| lvf.skewness),
},
}
.fmt_self::<_, _, C>("ocv_skewness_", key, f)?;
}
Ok(())
}
}