1#![allow(clippy::multiple_inherent_impl)]
4use crate::{
5 Ctx,
6 ast::{
7 self, BuilderScope, GroupComments, GroupFn, ParsingBuilder, fmt_comment_liberty,
8 },
9 common::f64_into_hash_ord_fn,
10 expression::logic,
11 table::{DisplayTableLookUp, DisplayValues, TableLookUp2D},
12};
13use core::ops::{Add, Mul, Not as _, Sub};
14use itertools::izip;
15use strum::{Display, EnumString};
16#[derive(
64 Debug, Clone, Copy, PartialEq, Display, EnumString, Default, Hash, Eq, PartialOrd, Ord
65)]
66#[derive(serde::Serialize, serde::Deserialize)]
67pub enum TimingSenseType {
68 #[strum(serialize = "positive_unate")]
74 PositiveUnate,
75 #[strum(serialize = "negative_unate")]
81 NegativeUnate,
82 #[strum(serialize = "non_unate")]
90 #[default]
91 NonUnate,
92}
93
94impl TimingSenseType {
95 #[must_use]
96 #[inline]
97 pub fn compute_edge(&self, pin_edge: &logic::Edge) -> Option<logic::Edge> {
98 match self {
99 Self::PositiveUnate => Some(*pin_edge),
100 Self::NegativeUnate => Some(pin_edge.not()),
101 Self::NonUnate => None,
102 }
103 }
104}
105crate::ast::impl_self_builder!(TimingSenseType);
106crate::ast::impl_simple!(TimingSenseType);
107
108#[derive(Debug, Clone)]
118#[derive(liberty_macros::Group)]
119#[mut_set::derive::item]
120#[derive(serde::Serialize, serde::Deserialize)]
121#[serde(bound = "C::Other: serde::Serialize + serde::de::DeserializeOwned")]
122pub struct CellDegradation<C: Ctx> {
123 #[liberty(name)]
125 #[id(borrow = str)]
126 pub name: String,
127 #[liberty(comments)]
129 comments: GroupComments,
130 #[liberty(extra_ctx)]
131 pub extra_ctx: C::Other,
132 #[liberty(attributes)]
134 pub attributes: ast::Attributes,
135 #[liberty(complex)]
140 pub index_1: Vec<f64>,
141 #[liberty(complex)]
146 pub values: Vec<f64>,
147}
148impl<C: Ctx> GroupFn<C> for CellDegradation<C> {}
149
150#[derive(Debug, Clone, Default)]
151#[derive(serde::Serialize, serde::Deserialize)]
152pub struct TimingTableLookUp<C: Ctx> {
153 pub extra_ctx: C::Table,
154 pub name: String,
155 pub comments: String,
156 pub index_1: Vec<f64>,
157 pub index_2: Vec<f64>,
158 pub size1: usize,
159 pub size2: usize,
160 pub values: Vec<f64>,
161 pub lvf_index_1: Vec<f64>,
164 pub lvf_index_2: Vec<f64>,
167 pub lvf_values: Vec<LVFValue>,
168}
169#[expect(
170 clippy::similar_names,
171 clippy::indexing_slicing,
172 clippy::arithmetic_side_effects
173)]
174impl<C: Ctx> TimingTableLookUp<C> {
175 #[inline]
176 #[expect(clippy::needless_pass_by_ref_mut)]
177 pub(crate) fn use_common_template(
178 table: &mut Option<Self>,
179 scope: &mut BuilderScope<C>,
180 ) {
181 if let Some(t) = table {
182 #[cfg(feature = "lut_template")]
183 crate::table::TableCtx::set_lut_template(
184 &mut t.extra_ctx,
185 scope.lu_table_template.get(&t.name),
186 );
187 }
188 }
189 #[inline]
190 const fn find_pos(len: usize, pos: usize) -> Option<(usize, usize)> {
191 if len <= 1 {
192 None
193 } else {
194 Some(if pos == 0 {
195 (0, 1)
196 } else if pos == len {
197 (len - 2, len - 1)
198 } else {
199 (pos - 1, pos)
200 })
201 }
202 }
203 #[inline]
204 fn get_value(&self, ix: usize, iy: usize) -> f64 {
205 self.values[ix * self.index_2.len() + iy]
206 }
207 #[inline]
208 fn get_lvf_value(&self, ix: usize, iy: usize) -> LVFValue {
209 self.lvf_values[ix * self.index_2.len() + iy]
210 }
211 #[must_use]
213 #[inline]
214 #[expect(clippy::float_arithmetic)]
215 pub fn lookup(&self, idx1: &f64, idx2: &f64) -> Option<f64> {
216 let idx1_ = f64_into_hash_ord_fn(idx1);
217 let idx2_ = f64_into_hash_ord_fn(idx2);
218 match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
219 Ok(i1_) => {
220 match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
221 Ok(i_1) => Some(self.get_value(i1_, i_1)),
222 Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
223 let q_1 = self.get_value(i1_, i_1);
224 let q_2 = self.get_value(i1_, i_2);
225 let x_1 = self.index_2[i_1];
226 let x_2 = self.index_2[i_2];
227 (q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
229 }),
230 }
231 }
232 Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
233 let x1_ = self.index_1[i1_];
234 let x2_ = self.index_1[i2_];
235 match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
236 Ok(i_1) => {
237 let q1_ = self.get_value(i1_, i_1);
238 let q2_ = self.get_value(i2_, i_1);
239 Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
241 }
242 Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
243 let q11 = self.get_value(i1_, i_1);
244 let q12 = self.get_value(i1_, i_2);
245 let q21 = self.get_value(i2_, i_1);
246 let q22 = self.get_value(i2_, i_2);
247 let x_1 = self.index_2[i_1];
248 let x_2 = self.index_2[i_2];
249 let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
251 let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
253 (q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
255 }),
256 }
257 }),
258 }
259 }
260 #[must_use]
261 #[inline]
262 #[expect(clippy::float_arithmetic)]
263 pub fn lookup_lvf(&self, idx1: &f64, idx2: &f64) -> Option<LVFValue> {
264 let idx1_ = f64_into_hash_ord_fn(idx1);
265 let idx2_ = f64_into_hash_ord_fn(idx2);
266 match self.index_1.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx1_)) {
267 Ok(i1_) => {
268 match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
269 Ok(i_1) => Some(self.get_lvf_value(i1_, i_1)),
270 Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
271 let q_1 = self.get_lvf_value(i1_, i_1);
272 let q_2 = self.get_lvf_value(i1_, i_2);
273 let x_1 = self.index_2[i_1];
274 let x_2 = self.index_2[i_2];
275 (q_2 - q_1).mul_add((idx2 - x_1) / (x_2 - x_1), q_1)
277 }),
278 }
279 }
280 Err(pos1) => Self::find_pos(self.index_1.len(), pos1).and_then(|(i1_, i2_)| {
281 let x1_ = self.index_1[i1_];
282 let x2_ = self.index_1[i2_];
283 match self.index_2.binary_search_by(|v| f64_into_hash_ord_fn(v).cmp(&idx2_)) {
284 Ok(i_1) => {
285 let q1_ = self.get_lvf_value(i1_, i_1);
286 let q2_ = self.get_lvf_value(i2_, i_1);
287 Some((q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_))
289 }
290 Err(pos2) => Self::find_pos(self.index_2.len(), pos2).map(|(i_1, i_2)| {
291 let q11 = self.get_lvf_value(i1_, i_1);
292 let q12 = self.get_lvf_value(i1_, i_2);
293 let q21 = self.get_lvf_value(i2_, i_1);
294 let q22 = self.get_lvf_value(i2_, i_2);
295 let x_1 = self.index_2[i_1];
296 let x_2 = self.index_2[i_2];
297 let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
299 let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
301 (q2_ - q1_).mul_add((idx1 - x1_) / (x2_ - x1_), q1_)
303 }),
304 }
305 }),
306 }
307 }
308}
309
310#[derive(serde::Serialize, serde::Deserialize)]
311#[derive(Debug, Default, Clone, Copy)]
312pub struct LVFValue {
313 pub mean: f64,
315 pub std_dev: f64,
316 pub skewness: f64,
317}
318impl LVFValue {
319 #[inline]
320 #[must_use]
321 pub fn mul_add(self, a: f64, b: Self) -> Self {
323 Self {
324 mean: self.mean.mul_add(a, b.mean),
325 std_dev: self.std_dev.mul_add(a, b.std_dev),
326 skewness: self.skewness.mul_add(a, b.skewness),
327 }
328 }
329}
330impl PartialEq for LVFValue {
331 #[inline]
332 fn eq(&self, other: &Self) -> bool {
333 f64_into_hash_ord_fn(&self.mean) == f64_into_hash_ord_fn(&other.mean)
334 && f64_into_hash_ord_fn(&self.std_dev) == f64_into_hash_ord_fn(&other.std_dev)
335 && f64_into_hash_ord_fn(&self.skewness) == f64_into_hash_ord_fn(&other.skewness)
336 }
337}
338#[expect(clippy::float_arithmetic)]
339impl Add for LVFValue {
340 type Output = Self;
341 #[inline]
342 fn add(self, rhs: Self) -> Self::Output {
343 Self {
344 mean: self.mean + rhs.mean,
345 std_dev: self.std_dev + rhs.std_dev,
346 skewness: self.skewness + rhs.skewness,
347 }
348 }
349}
350#[expect(clippy::float_arithmetic)]
351impl Sub for LVFValue {
352 type Output = Self;
353 #[inline]
354 fn sub(self, rhs: Self) -> Self::Output {
356 Self {
357 mean: self.mean - rhs.mean,
358 std_dev: self.std_dev - rhs.std_dev,
360 skewness: self.skewness - rhs.skewness,
361 }
362 }
363}
364#[expect(clippy::float_arithmetic)]
365impl Mul<f64> for LVFValue {
366 type Output = Self;
367 #[inline]
368 fn mul(self, rhs: f64) -> Self::Output {
369 Self {
370 mean: self.mean * rhs,
371 std_dev: self.std_dev * rhs,
372 skewness: self.skewness * rhs,
373 }
374 }
375}
376
377impl<C: Ctx> ParsingBuilder<C> for Option<TimingTableLookUp<C>> {
378 type Builder = (
380 Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
382 Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
384 Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
386 Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
388 );
389 #[inline]
390 #[expect(clippy::float_arithmetic, clippy::arithmetic_side_effects)]
391 fn build(builder: Self::Builder, _scope: &mut BuilderScope<C>) -> Self {
392 #[inline]
393 fn eq_index<C: Ctx>(
394 lhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
395 rhs: &<TableLookUp2D<C> as ParsingBuilder<C>>::Builder,
396 ) -> bool {
397 lhs.index_1 == rhs.index_1 && lhs.index_2 == rhs.index_2
398 }
399 let mut out: TimingTableLookUp<C> = match builder {
400 (Some(_value), Some(_mean_shift), Some(_std_dev), Some(_skewness)) => {
401 let lvf_nomial_same_index = eq_index(&_value, &_mean_shift);
402 let valid_lvf_index =
403 eq_index(&_mean_shift, &_std_dev) && eq_index(&_std_dev, &_skewness);
404 let (lvf_values, comments) = if valid_lvf_index {
405 (
406 izip!(
407 _value.values.inner.iter(),
408 _mean_shift.values.inner,
409 _std_dev.values.inner,
410 _skewness.values.inner
411 )
412 .map(|(value, mean_shift, std_dev, skewness)| {
413 let mean = value + mean_shift;
414 LVFValue { mean, std_dev, skewness }
415 })
416 .collect(),
417 String::new(),
418 )
419 } else {
420 crate::error!("LVF LUTs' index mismatch");
421 (Vec::new(), String::from("LVF LUTs' index mismatch"))
422 };
423 TimingTableLookUp {
424 extra_ctx: C::Table::default(),
425 name: _value.name,
426 comments,
427 index_1: _value.index_1,
428 index_2: _value.index_2,
429 size1: _value.values.size1,
430 size2: _value.values.size2,
431 values: _value.values.inner,
432 lvf_index_1: if lvf_nomial_same_index {
433 Vec::new()
434 } else {
435 _mean_shift.index_1
436 },
437 lvf_index_2: if lvf_nomial_same_index {
438 Vec::new()
439 } else {
440 _mean_shift.index_2
441 },
442 lvf_values,
443 }
444 }
445 (Some(_value), None, None, None) => TimingTableLookUp {
446 extra_ctx: C::Table::default(),
447 name: _value.name,
448 comments: String::new(),
449 index_1: _value.index_1,
450 index_2: _value.index_2,
451 size1: _value.values.size1,
452 size2: _value.values.size2,
453 values: _value.values.inner,
454 lvf_index_1: Vec::new(),
455 lvf_index_2: Vec::new(),
456 lvf_values: Vec::new(),
457 },
458 _ => return None,
459 };
460 if out.size2 == 1 && out.values.len() == out.index_1.len() * out.index_2.len() {
461 out.size1 = out.index_1.len();
462 out.size2 = out.index_2.len();
463 }
464 Some(out)
465 }
466}
467impl<C: Ctx> TimingTableLookUp<C> {
468 #[inline]
469 #[expect(clippy::float_arithmetic)]
470 pub(crate) fn fmt_liberty<T: core::fmt::Write, I: ast::Indentation>(
471 &self,
472 key: &str,
473 f: &mut ast::CodeFormatter<'_, T, I>,
474 ) -> core::fmt::Result {
475 fmt_comment_liberty(Some(&self.comments), f)?;
476 DisplayTableLookUp {
477 name: &self.name,
478 index_1: &self.index_1,
479 index_2: &self.index_2,
480 values: DisplayValues {
481 len: self.values.len(),
482 size1: self.size1,
483 inner: self.values.iter().copied(),
484 },
485 }
486 .fmt_self::<_, _, C>("", key, f)?;
487 if !self.lvf_values.is_empty() {
488 let mismatch_index = !self.lvf_index_1.is_empty();
489 DisplayTableLookUp {
490 name: &self.name,
491 index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
492 index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
493 values: DisplayValues {
494 len: self.values.len(),
495 size1: self.size1,
496 inner: izip!(self.values.iter(), self.lvf_values.iter())
497 .map(|(value, lvf)| lvf.mean - value),
498 },
499 }
500 .fmt_self::<_, _, C>("ocv_mean_shift_", key, f)?;
501 DisplayTableLookUp {
502 name: &self.name,
503 index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
504 index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
505 values: DisplayValues {
506 len: self.values.len(),
507 size1: self.size1,
508 inner: self.lvf_values.iter().map(|lvf| lvf.std_dev),
509 },
510 }
511 .fmt_self::<_, _, C>("ocv_std_dev_", key, f)?;
512 DisplayTableLookUp {
513 name: &self.name,
514 index_1: if mismatch_index { &self.lvf_index_1 } else { &self.index_1 },
515 index_2: if mismatch_index { &self.lvf_index_2 } else { &self.index_2 },
516 values: DisplayValues {
517 len: self.values.len(),
518 size1: self.size1,
519 inner: self.lvf_values.iter().map(|lvf| lvf.skewness),
520 },
521 }
522 .fmt_self::<_, _, C>("ocv_skewness_", key, f)?;
523 }
524 Ok(())
525 }
526}