#![allow(clippy::multiple_inherent_impl)]
use core::ops::{Add, Mul, Not as _, Sub};
use crate::{
ast::{
self, fmt_comment_liberty, BuilderScope, GroupComments, GroupFn, ParseScope,
ParsingBuilder, SimpleAttri,
},
common::{
f64_into_hash_ord_fn,
table::{DisplayTableLookUp, DisplayValues, TableLookUp},
},
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 SimpleAttri for TimingSenseType {
#[inline]
fn nom_parse<'a>(i: &'a str, scope: &mut ParseScope) -> ast::SimpleParseRes<'a, Self> {
ast::nom_parse_from_str(i, scope)
}
}
pub type Mode = [String; 2];
#[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 for CellDegradation<C> {}
#[derive(Debug, Clone, Default)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct TimingTableLookUp<C: Ctx> {
pub extra_ctx: C::Table,
pub name: Option<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_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 for Option<TimingTableLookUp<C>> {
type Builder = (
Option<<TableLookUp<C> as ParsingBuilder>::Builder>,
Option<<TableLookUp<C> as ParsingBuilder>::Builder>,
Option<<TableLookUp<C> as ParsingBuilder>::Builder>,
Option<<TableLookUp<C> as ParsingBuilder>::Builder>,
);
#[inline]
#[expect(clippy::float_arithmetic)]
fn build(builder: Self::Builder, _scope: &mut BuilderScope) -> Self {
#[inline]
fn eq_index<C: Ctx>(
lhs: &<TableLookUp<C> as ParsingBuilder>::Builder,
rhs: &<TableLookUp<C> as ParsingBuilder>::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_values, comments) = if eq_index(&_value, &_mean_shift)
&& eq_index(&_mean_shift, &_std_dev)
&& eq_index(&_std_dev, &_skewness)
{
(
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"))
};
Some(TimingTableLookUp {
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_values,
extra_ctx: Default::default(),
})
}
(Some(_value), None, None, None) => Some(TimingTableLookUp {
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_values: Vec::new(),
extra_ctx: Default::default(),
}),
_ => 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("", key, f)?;
if !self.lvf_values.is_empty() {
DisplayTableLookUp {
name: &self.name,
index_1: &self.index_1,
index_2: &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("ocv_mean_shift_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: &self.index_1,
index_2: &self.index_2,
values: DisplayValues {
size1: self.size1,
inner: self.lvf_values.iter().map(|lvf| lvf.std_dev),
},
}
.fmt_self("ocv_std_dev_", key, f)?;
DisplayTableLookUp {
name: &self.name,
index_1: &self.index_1,
index_2: &self.index_2,
values: DisplayValues {
size1: self.size1,
inner: self.lvf_values.iter().map(|lvf| lvf.skewness),
},
}
.fmt_self("ocv_skewness_", key, f)?;
}
Ok(())
}
}