liberty_db/timing/
items.rs

1//! All item structure inside
2//! `Timing`.
3#![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/// The `timing_sense` attribute describes the way an input pin logically affects an output pin.
17///
18/// <a name ="reference_link" href="
19/// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=328.32+329.2+330.2&end=328.33+329.39+330.6
20/// ">Reference</a>
21///
22/// #### Syntax
23/// `timing_sense : positive_unate | negative_unate | non_unate ;`
24///
25/// `positive_unate`: Combines incoming rise delays with local rise delays and
26/// compares incoming fall delays with local fall delays.
27///
28/// `negative_unate`: Combines incoming rise delays with local fall delays and
29/// compares incoming fall delays with local rise delays.
30///
31/// `non_unate`: Combines local delays with the worst-case incoming delay value.
32/// The non-unate timing sense represents a function whose output value change cannot
33/// be determined from the direction of the change in the input value.
34///
35/// Timing sense is derived from the logic function of a pin. For example, the value derived for
36/// an AND gate is `positive_unate`, the value for a NAND gate is `negative_unate`, and the value
37/// for an XOR gate is `non_unate`.
38///
39/// A function is said to be unate if a rising (falling) change on a positive (negative) unate
40/// input variable causes the output function variable to rise (fall) or not change.
41/// For a non-unate variable, further state information is required to determine the effects of
42/// a particular state transition.
43///
44/// You can specify half-unate sequential timing arcs if the `timing_type` value is either
45/// `rising_edge` or `falling_edge` and the `timing_sense` value is either `positive_unate`
46/// or `negative_unate`.
47/// + In the case of `rising_edge` and `positive_unate` values, only the `cell_rise` and `rise_transition` information is required.
48/// + In the case of `rising_edge` and `negative_unate` values, only the `cell_fall` and `fall_transition` information is required.
49/// + In the case of `falling_edge` and `positive_unate` values, only the `cell_rise` and `rise_transition` information is required.
50/// + In the case of `falling_edge` and `negative_unate` values, only the `cell_fall` and `fall_transition` information is required.
51///
52/// Do not define the `timing_sense` value of a pin, except when you need to override the derived value
53/// or when you are characterizing a noncombinational gate such as a three-state component. For example,
54/// you might want to define the timing sense manually when you model multiple paths between
55/// an input pin and an output pin, such as in an XOR gate.
56///
57/// It is possible that one path is positive unate while another is negative unate. In this case,
58/// the first timing arc is given a `positive_unate` designation and the second is given a `negative_unate`
59/// designation.
60///
61/// Timing arcs with a timing type of `clear` or `preset` require a `timing_sense` attribute.
62/// If `related_pin` is an output pin, you must define a `timing_sense` attribute for that pin.
63#[derive(
64  Debug, Clone, Copy, PartialEq, Display, EnumString, Default, Hash, Eq, PartialOrd, Ord
65)]
66#[derive(serde::Serialize, serde::Deserialize)]
67pub enum TimingSenseType {
68  /// Combines incoming `rise` delays with local `rise` delays
69  /// and compares incoming `fall` delays with local `fall` delays.
70  /// <a name ="reference_link" href="
71  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.5&end=329.6
72  /// ">Reference</a>
73  #[strum(serialize = "positive_unate")]
74  PositiveUnate,
75  /// Combines incoming `rise` delays with local `fall` delays
76  /// and compares incoming `fall` delays with local `rise` delays.
77  /// <a name ="reference_link" href="
78  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.8&end=329.9
79  /// ">Reference</a>
80  #[strum(serialize = "negative_unate")]
81  NegativeUnate,
82  /// Combines local delays with the `worst-case` incoming delay value.
83  /// The non-unate timing sense represents a function whose
84  /// output value change cannot be determined from the direction
85  /// of the change in the input value.
86  /// <a name ="reference_link" href="
87  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=329.11&end=329.13
88  /// ">Reference</a>
89  #[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/// The `cell_degradation`  group describes a cell performance degradation
109/// design rule for compiling a design.
110///
111/// A cell degradation design rule specifies the maximum capacitive load
112/// a cell can drive without causing cell performance degradation during the fall transition.
113/// <a name ="reference_link" href="
114/// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=347.33&end=347.35
115/// ">Reference</a>
116///
117#[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  /// name
124  #[liberty(name)]
125  #[id(borrow = str)]
126  pub name: String,
127  /// group comments
128  #[liberty(comments)]
129  comments: GroupComments,
130  #[liberty(extra_ctx)]
131  pub extra_ctx: C::Other,
132  /// group undefined attributes
133  #[liberty(attributes)]
134  pub attributes: ast::Attributes,
135  /// /* lookup table */
136  /// <a name ="reference_link" href="
137  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=348.6&end=348.7
138  /// ">Reference</a>
139  #[liberty(complex)]
140  pub index_1: Vec<f64>,
141  /// /* lookup table */
142  /// <a name ="reference_link" href="
143  /// https://zao111222333.github.io/liberty-db/2020.09/reference_manual.html?field=null&bgn=348.6&end=348.7
144  /// ">Reference</a>
145  #[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  /// when `!lvf_values.is_empty() && lvf_index_1.is_empty()`
162  /// directly use `index_1`
163  pub lvf_index_1: Vec<f64>,
164  /// when `!lvf_values.is_empty() && lvf_index_2.is_empty()`
165  /// directly use `index_1`
166  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  /// The linear interpolation & extrapolation
212  #[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_1 + (q_2 - q_1) * ((idx2 - x_1) / (x_2 - x_1))
228            (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(q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_)))
240            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_ = q11 + (q12 - q11) * ((idx2 - x_1) / (x_2 - x_1));
250            let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
251            // let q2_ = q21 + (q22 - q21) * ((idx2 - x_1) / (x_2 - x_1));
252            let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
253            // q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_))
254            (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_1 + (q_2 - q_1) * ((idx2 - x_1) / (x_2 - x_1))
276            (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(q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_)))
288            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_ = q11 + (q12 - q11) * ((idx2 - x_1) / (x_2 - x_1));
298            let q1_ = (q12 - q11).mul_add((idx2 - x_1) / (x_2 - x_1), q11);
299            // let q2_ = q21 + (q22 - q21) * ((idx2 - x_1) / (x_2 - x_1));
300            let q2_ = (q22 - q21).mul_add((idx2 - x_1) / (x_2 - x_1), q21);
301            // q1_ + (q2_ - q1_) * ((idx1 - x1_) / (x2_ - x1_))
302            (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  /// `mean` = `nominal` + `mean_shift`
314  pub mean: f64,
315  pub std_dev: f64,
316  pub skewness: f64,
317}
318impl LVFValue {
319  #[inline]
320  #[must_use]
321  /// self * a + b
322  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  /// TODO: check
355  fn sub(self, rhs: Self) -> Self::Output {
356    Self {
357      mean: self.mean - rhs.mean,
358      // TODO: check - or + ?
359      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  /// `value`, `mean_shift`, `std_dev`, `skewness`
379  type Builder = (
380    // value
381    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
382    // mean_shift
383    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
384    // std_dev
385    Option<<TableLookUp2D<C> as ParsingBuilder<C>>::Builder>,
386    // skewness
387    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}