mod import;
mod import_timing_table;
mod propagation_pattern;
use fnv::FnvHashMap;
use smallvec::SmallVec;
use uom::si::{
capacitance::farad,
f64::{Capacitance, Time},
time::second,
};
use self::propagation_pattern::{PropagationPattern, PropagationPatterns};
use super::liberty_util::*;
use crate::{
traits::{
cell_constraint_model::CellConstraintArc,
cell_logic_model::{OutputFunction, Unateness},
timing_library::{RiseFall, TimingLibrary},
},
CellDelayArc, ConstraintArcArg, DelayArcArg, SetupHold,
};
use itertools::Itertools;
use liberty_io::{
boolean::{parse_boolean_function, BooleanExpr},
CapacitiveLoadUnit, Group, TimeUnit,
};
use std::{collections::HashMap, hash::Hash, str::FromStr};
type SlewLut = Interp<Time, Capacitance, Time>;
type DelayLut = Interp<Time, Capacitance, Time>;
type ConstraintLut = Interp<Time, Time, Time>;
#[derive(Default, Clone, Debug)]
pub struct DelayArc {
pub timing_sense: Unateness,
pub timing_type: TimingType,
rise_transition: Option<SlewLut>,
fall_transition: Option<SlewLut>,
cell_rise: Option<DelayLut>,
cell_fall: Option<DelayLut>,
}
#[derive(Default, Clone, Debug)]
pub struct ConstraintArc {
pub timing_type: TimingType,
pub rise_constraint: Option<ConstraintLut>,
pub fall_constraint: Option<ConstraintLut>,
}
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, Hash, Default)]
pub enum TimingType {
#[default]
Combinational,
CombinationalRise,
CombinationalFall,
ThreeStateDisable,
ThreeStateEnable,
ThreeStateDisableRise,
ThreeStateDisableFall,
ThreeStateEnableRise,
ThreeStateEnableFall,
RisingEdge,
FallingEdge,
Preset,
Clear,
HoldRising,
HoldFalling,
SetupRising,
SetupFalling,
RecoveryRising,
RecoveryFalling,
RemovalRising,
RemovalFalling,
}
impl DelayArc {
pub fn transition(&self, output_edge: RiseFall) -> Option<&SlewLut> {
self.debug_sanity_check();
match output_edge {
RiseFall::Rise => self.rise_transition.as_ref(),
RiseFall::Fall => self.fall_transition.as_ref(),
}
}
pub fn delay(&self, output_edge: RiseFall) -> Option<&DelayLut> {
self.debug_sanity_check();
match output_edge {
RiseFall::Rise => self.cell_rise.as_ref(),
RiseFall::Fall => self.cell_fall.as_ref(),
}
}
fn debug_sanity_check(&self) {
}
}
#[derive(Clone, Debug)]
pub struct LibertyTimingLibrary<CellId, PinId> {
table_templates: HashMap<String, LuTableTemplate>,
time_unit: Time,
capacitive_load_unit: Capacitance,
pub(crate) cells: HashMap<CellId, Cell<PinId>>,
}
#[derive(Clone, Debug)]
pub(crate) struct Cell<PinId> {
flip_flops: Vec<FlipFlop<PinId>>,
latches: Vec<Latch<PinId>>,
pub(crate) pins: FnvHashMap<PinId, Pin<PinId>>,
cell_name: String,
pub(super) delay_arcs: SmallVec<[CellDelayArc<PinId>; 4]>,
}
impl<PinId> Cell<PinId>
where
PinId: Clone,
{
pub(super) fn run_precomputations(&mut self) {
self.precompute_delay_arcs()
}
fn precompute_delay_arcs(&mut self) {
self.delay_arcs = self
.pins
.iter()
.flat_map(move |(output_pin, pin)| {
pin.timing
.delay_arcs
.iter()
.flat_map(move |(related_pin, delay_arc)| {
let prop_patterns =
PropagationPatterns::new(delay_arc.timing_type, delay_arc.timing_sense);
let related_edge = related_pin.1;
let output_edges = [RiseFall::Rise, RiseFall::Fall].into_iter().filter(
move |&output_edge| {
let current_edge =
PropagationPattern::from_risefall(related_edge, output_edge);
prop_patterns.contains(¤t_edge)
},
);
output_edges.map(|output_edge| CellDelayArc {
output_pin: (output_pin.clone(), output_edge),
input_pin: related_pin.clone(),
})
})
})
.collect()
}
}
#[derive(Clone, Debug)]
pub(crate) struct FlipFlop<PinId> {
state_variable: String,
state_variable_inv: String,
next_state: BooleanExpr<PinId>,
clocked_on: BooleanExpr<PinId>,
clocked_on_also: Option<BooleanExpr<PinId>>,
num_bits: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct Latch<PinId> {
state_variable: String,
state_variable_inv: String,
data_in: BooleanExpr<PinId>,
enable: BooleanExpr<PinId>,
}
#[derive(Default, Debug, Clone)]
pub struct Pin<PinId> {
pin_name: String,
pub(crate) output_function: OutputFunction<PinId>,
pub(crate) capacitance: Capacitance,
pub(crate) capacitance_rise: Capacitance,
pub(crate) capacitance_fall: Capacitance,
pub(crate) timing: PinTiming<PinId>,
}
#[derive(Debug, Clone)]
pub struct PinTiming<PinId> {
delay_arcs: FnvHashMap<(PinId, RiseFall), DelayArc>,
pub(crate) hold_arcs: FnvHashMap<(PinId, RiseFall), ConstraintArc>,
pub(crate) setup_arcs: FnvHashMap<(PinId, RiseFall), ConstraintArc>,
}
impl<PinId> PinTiming<PinId>
where
PinId: Hash + Eq,
{
pub fn get_delay_lut(&self, input_pin: &(PinId, RiseFall)) -> Option<&DelayArc> {
self.delay_arcs.get(input_pin)
}
pub fn get_constraint_lut(
&self,
constraint_type: SetupHold,
constrained_edge: RiseFall,
related_pin: &(PinId, RiseFall),
) -> Option<&ConstraintLut> {
use SetupHold::*;
let arcs = match constraint_type {
Hold => &self.hold_arcs,
Setup => &self.setup_arcs,
};
arcs.get(related_pin)
.and_then(|constraint_arc| match constrained_edge {
RiseFall::Rise => constraint_arc.rise_constraint.as_ref(),
RiseFall::Fall => constraint_arc.fall_constraint.as_ref(),
})
}
}
impl<PinId> Default for PinTiming<PinId> {
fn default() -> Self {
Self {
delay_arcs: Default::default(),
hold_arcs: Default::default(),
setup_arcs: Default::default(),
}
}
}
#[derive(Clone, Debug)]
struct LuTableTemplate {
var1: String,
var2: Option<String>,
size: (usize, usize),
}
struct LibraryImportContext<'a, CellId, PinId> {
cell_by_name: &'a dyn Fn(&str) -> Option<CellId>,
pin_by_name: &'a dyn Fn(&CellId, &str) -> Option<PinId>,
}
struct CellImportContext<'a, CellId, PinId> {
library_context: &'a LibraryImportContext<'a, CellId, PinId>,
cell_id: &'a CellId,
cell_name: &'a str,
}
struct PinImportContext<'a, CellId, PinId> {
cell_context: &'a CellImportContext<'a, CellId, PinId>,
pin_id: &'a PinId,
pin_name: &'a str,
}
impl<CellId, PinId> LibertyTimingLibrary<CellId, PinId>
where
CellId: Hash + Eq + Clone,
PinId: Hash + Eq + Clone,
{
pub fn new(
lib: &Group,
cell_by_name: impl Fn(&str) -> Option<CellId>,
pin_by_name: impl Fn(&CellId, &str) -> Option<PinId>,
) -> Result<Self, LibertyErr> {
let library_import_context = LibraryImportContext {
cell_by_name: &cell_by_name,
pin_by_name: &pin_by_name,
};
if lib.name != "library" {
Err(LibertyErr::Other(format!(
"Group must be a `library` group but it is `{}`",
lib.name
)))?;
}
let mut l = Self {
table_templates: Default::default(),
cells: Default::default(),
time_unit: Default::default(),
capacitive_load_unit: Default::default(),
};
l.init_units(lib)?;
Self::check_delay_model(lib)?;
l.table_templates = import::read_template_tables(lib)?;
l.read_cells(lib, library_import_context)?;
Ok(l)
}
fn check_delay_model(lib: &Group) -> Result<(), LibertyErr> {
let delay_model = lib
.get_simple_attribute("delay_model")
.and_then(|v| v.as_str());
if delay_model != Some("table_lookup") {
log::error!(
"Delay model is not supported. Must be `table_lookup`. delay_model = {:?}",
delay_model
);
Err(LibertyErr::UnsupportedDelayModel(
delay_model.unwrap_or("<unknown>").to_string(),
))?;
}
Ok(())
}
fn init_units(&mut self, lib: &Group) -> Result<(), LibertyErr> {
{
let time_unit_str = lib
.get_simple_attribute("time_unit")
.ok_or(LibertyErr::UnitNotDefined("time_unit"))?
.as_str()
.ok_or(LibertyErr::UnitNotDefined("time_unit is not a string"))?;
let time_unit_sec = TimeUnit::from_str(time_unit_str)
.map_err(|_| LibertyErr::UnitNotDefined("Failed to parse time_unit."))?;
self.time_unit = Time::new::<second>(time_unit_sec.as_seconds());
}
{
let cap_unit = lib
.get_complex_attribute("capacitive_load_unit")
.ok_or(LibertyErr::UnitNotDefined("capacitive_load_unit"))?;
let c = CapacitiveLoadUnit::try_from(cap_unit.as_slice())
.map_err(|_| LibertyErr::UnitNotDefined("capacitive_load_unit is malformed"))?;
self.capacitive_load_unit = Capacitance::new::<farad>(c.as_farad());
}
Ok(())
}
}
impl<CellId, PinId> LibertyTimingLibrary<CellId, PinId>
where
CellId: Hash + Eq,
PinId: Hash + Eq + Clone,
{
pub fn get_pin(&self, cell: &CellId, output_pin: &PinId) -> Option<&Pin<PinId>> {
self.cells.get(cell).and_then(|c| c.pins.get(output_pin))
}
pub(crate) fn get_delay_arc(&self, arc: DelayArcArg<CellId, PinId>) -> Option<&DelayArc> {
self.get_pin(arc.cell, &arc.arc.output_pin.0)
.and_then(|p| p.timing.get_delay_lut(&arc.arc.input_pin))
}
pub(crate) fn get_hold_lut(
&self,
cell: &CellId,
arc: &CellConstraintArc<PinId>,
) -> Option<&ConstraintLut> {
self.get_pin(cell, &arc.constrained_pin.0).and_then(|p| {
p.timing
.get_constraint_lut(SetupHold::Hold, arc.constrained_pin.1, &arc.related_pin)
})
}
pub(crate) fn get_setup_lut(
&self,
cell: &CellId,
arc: &CellConstraintArc<PinId>,
) -> Option<&ConstraintLut> {
self.get_pin(cell, &arc.constrained_pin.0).and_then(|p| {
p.timing
.get_constraint_lut(SetupHold::Setup, arc.constrained_pin.1, &arc.related_pin)
})
}
}
fn get_cell_name(cell_group: &Group) -> Result<&str, LibertyErr> {
let cell_name = cell_group
.arguments
.first()
.and_then(|v| v.as_str())
.ok_or_else(|| {
let msg = "Cell group has no name argument.";
log::error!("{}", msg);
LibertyErr::Other(msg.to_string())
})?;
Ok(cell_name)
}
impl<CellId, PinId> TimingLibrary for LibertyTimingLibrary<CellId, PinId>
where
CellId: Hash + Eq,
PinId: Hash + Eq + Clone,
{
type CellId = CellId;
type PinId = PinId;
fn get_slew(
&self,
arc: DelayArcArg<CellId, PinId>,
input_slew: Time,
output_capacitance: Capacitance,
) -> Option<Time> {
let edge_polarity = arc.arc.output_pin.1;
self.get_delay_arc(arc)
.and_then(|d| d.transition(edge_polarity))
.map(|f| {
f.eval2d((output_capacitance, input_slew))
})
}
fn get_cell_delay(
&self,
arc: DelayArcArg<CellId, PinId>,
input_slew: Time,
output_capacitance: Capacitance,
) -> Option<Time> {
let edge_polarity = arc.arc.output_pin.1;
self.get_delay_arc(arc)
.and_then(|d| d.delay(edge_polarity))
.map(|f| f.eval2d((output_capacitance, input_slew)))
}
fn get_hold_constraint(
&self,
arc: ConstraintArcArg<CellId, PinId>,
related_pin_transition: Time,
constrained_pin_transition: Time,
output_load: Capacitance,
) -> Option<Time> {
self.get_hold_lut(arc.cell, arc.arc)
.map(|f| f.eval2d((related_pin_transition, constrained_pin_transition)))
}
fn get_setup_constraint(
&self,
arc: ConstraintArcArg<CellId, PinId>,
related_pin_transition: Time,
constrained_pin_transition: Time,
output_load: Capacitance,
) -> Option<Time> {
self.get_setup_lut(arc.cell, arc.arc)
.map(|f| f.eval2d((related_pin_transition, constrained_pin_transition)))
}
}
#[test]
fn test_load_timing_library_freepdk45() {
use std::fs::File;
use std::io::BufReader;
let f = File::open("./tests/data/freepdk45/gscl45nm.lib").unwrap();
let mut buf = BufReader::new(f);
let result = liberty_io::read_liberty_bytes(&mut buf);
let library = result.unwrap();
assert_eq!(library.name.to_string(), "library");
assert_eq!(library.arguments[0].to_string(), "gscl45nm");
let cell_by_name = |name: &str| -> Option<String> { Some(name.into()) };
let pin_by_name = |cell_id: &String, name: &str| -> Option<(String, String)> {
Some((cell_id.clone(), name.into()))
};
let timing_library = LibertyTimingLibrary::new(&library, cell_by_name, pin_by_name).unwrap();
}
#[test]
fn test_lut_variable_ordering() {
use crate::traits::CellDelayArc;
let data = r#"
library() {
time_unit: "1ns" ;
capacitive_load_unit (1, pf);
delay_model: table_lookup;
lu_table_template(delay_template_2x3) {
variable_1 : total_output_net_capacitance;
variable_2 : input_net_transition;
index_1 ("1000.0, 1001.0");
index_2 ("1000.0, 1001.0, 1002.0");
}
lu_table_template(delay_template_2x3_swapped_vars) {
variable_1 : input_net_transition;
variable_2 : total_output_net_capacitance;
index_1 ("1000.0, 1001.0, 1002.0");
index_2 ("1000.0, 1001.0");
}
cell(INVX1) {
pin(Y) {
timing() {
related_pin: "A";
cell_rise(delay_template_2x3) {
index_1: "1.0, 2.0";
index_2: "1.0, 2.0, 3.0";
values (
"1.0, 1.1, 1.2", \
"1.1, 1.3, 1.5"
);
}
cell_fall(delay_template_2x3_swapped_vars) { // Use other variable ordering!
index_1: "1.0, 2.0, 3.0";
index_2: "1.0, 2.0";
values (
"1.0, 1.1", \
"1.1, 1.3", \
"1.2, 1.5"
);
}
}
}
}
}
"#;
let result = liberty_io::read_liberty_chars(data.chars());
let library = result.unwrap();
let cell_by_name = |name: &str| -> Option<String> { Some(name.into()) };
let pin_by_name = |cell_id: &String, name: &str| -> Option<(String, String)> {
Some((cell_id.clone(), name.into()))
};
let timing_library = LibertyTimingLibrary::new(&library, cell_by_name, pin_by_name).unwrap();
let invx1 = cell_by_name("INVX1").unwrap();
dbg!(&timing_library);
let arc_a_y = timing_library
.get_delay_arc(DelayArcArg {
cell: &invx1,
arc: &CellDelayArc {
input_pin: (pin_by_name(&invx1, "A").unwrap(), RiseFall::Rise),
output_pin: (pin_by_name(&invx1, "Y").unwrap(), RiseFall::Fall),
},
})
.unwrap();
let cell_rise = arc_a_y.cell_rise.as_ref().unwrap();
let cell_fall = arc_a_y.cell_fall.as_ref().unwrap();
use uom::si::{capacitance::picofarad, time::nanosecond};
let ns = Time::new::<nanosecond>;
let pf = Capacitance::new::<picofarad>;
assert!((cell_rise.eval2d((pf(2.0), ns(3.0))) - ns(1.5)).abs() < ns(1e-6));
assert!((cell_rise.eval2d((pf(3.0), ns(2.0))) - ns(1.5)).abs() < ns(1e-6)); }