use uom::si::{
capacitance::farad,
f64::{Capacitance, Time},
time::second,
};
use super::liberty_util::*;
use crate::traits::timing_library::{EdgePolarity, TimingLibrary};
use itertools::Itertools;
use liberty_io::{CapacitiveLoadUnit, Group, TimeUnit};
use std::{collections::HashMap, 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 rising_edge: bool,
pub falling_edge: bool,
pub rise_transition: Option<SlewLut>,
pub fall_transition: Option<SlewLut>,
pub cell_rise: Option<DelayLut>,
pub cell_fall: Option<DelayLut>,
}
#[derive(Default, Clone, Debug)]
pub struct ConstraintArc {
pub rise_constraint: Option<ConstraintLut>,
pub fall_constraint: Option<ConstraintLut>,
}
#[derive(Clone, Debug)]
pub struct LibertyTimingLibrary<'a> {
pub(crate) lib: &'a Group,
table_templates: HashMap<String, LuTableTemplate>,
time_unit: Time,
capacitive_load_unit: Capacitance,
pub(crate) cells: HashMap<String, Cell>,
}
#[derive(Clone, Default, Debug)]
pub(crate) struct Cell {
pub(crate) pins: HashMap<String, Pin>,
}
#[derive(Default, Debug, Clone)]
pub struct Pin {
pub(crate) capacitance: Capacitance,
pub(crate) delay_arcs: HashMap<String, DelayArc>,
pub(crate) hold_rising: HashMap<String, ConstraintArc>,
pub(crate) hold_falling: HashMap<String, ConstraintArc>,
pub(crate) setup_rising: HashMap<String, ConstraintArc>,
pub(crate) setup_falling: HashMap<String, ConstraintArc>,
}
#[derive(Clone, Debug)]
struct LuTableTemplate {
var1: String,
var2: Option<String>,
size: (usize, usize),
}
fn read_template_tables(lib: &Group) -> Result<HashMap<String, LuTableTemplate>, LibertyErr> {
assert_eq!(
lib.name, "library",
"Expected a library group, got '{}'.",
lib.name
);
let mut table_templates = HashMap::new();
for template in lib.find_groups_by_name("lu_table_template") {
let template_name = template.arguments.first().ok_or_else(|| {
log::error!("lu_table_template has no name argument.");
LibertyErr::InvalidLuTableTemplate("<noname>".to_string())
})?;
let variable_1 = template
.get_simple_attribute("variable_1")
.and_then(|v| v.as_str())
.ok_or_else(|| {
log::error!(
"lu_table_template({}): `variable_1` is not defined.",
template_name
);
LibertyErr::InvalidLuTableTemplate(template_name.to_string())
})?;
let variable_2 = template
.get_simple_attribute("variable_2")
.and_then(|v| v.as_str());
let index1 = template
.get_simple_attribute("index_1")
.and_then(|v| v.as_str())
.map(liberty_io::util::parse_float_array)
.ok_or_else(|| {
log::error!(
"`{}` is not well formatted in {}.",
"index_1",
template_name.to_string()
);
LibertyErr::InvalidLuTableTemplate(template_name.to_string())
})?
.map_err(|_| {
log::error!(
"`{}` is not well formatted in {}.",
"index_1",
template_name.to_string()
);
LibertyErr::InvalidLuTableTemplate(template_name.to_string())
})?;
let index1_size = index1.len();
let index2_size = if variable_2.is_some() {
let index2 = template
.get_simple_attribute("index_2")
.and_then(|v| v.as_str())
.map(liberty_io::util::parse_float_array)
.ok_or_else(|| {
log::error!(
"`{}` is not well formatted in {}.",
"index_2",
template_name.to_string()
);
LibertyErr::InvalidLuTableTemplate(template_name.to_string())
})?
.map_err(|_| {
log::error!(
"`{}` is not well formatted in {}.",
"index_2",
template_name.to_string()
);
LibertyErr::InvalidLuTableTemplate(template_name.to_string())
})?;
index2.len()
} else {
1
};
let template = LuTableTemplate {
var1: variable_1.to_string(),
var2: variable_2.map(|v| v.to_string()),
size: (index1_size, index2_size),
};
table_templates.insert(template_name.to_string(), template);
}
Ok(table_templates)
}
fn get_interpolated_timing_table(
timing_group: &Group,
table_group: &Group,
table_templates: &HashMap<String, LuTableTemplate>,
required_var1_name: &str,
required_var2_name: &str,
cell_name: &str,
pin_name: &str,
) -> Result<Interp, LibertyErr> {
assert_eq!(
timing_group.name, "timing",
"Expected a timing group. Found: '{}'",
timing_group.name
);
let template_name = table_group
.arguments
.first()
.and_then(|v| v.as_str())
.ok_or_else(|| {
let msg = format!(
"Timing table {} has no template name defined in cell {}, pin {}.",
table_group.name, cell_name, pin_name
);
log::error!("{}", msg);
LibertyErr::Other(msg)
})?;
let mut interp = get_timing_table(
timing_group,
&table_group.name,
"index_1",
"index_2",
"values",
)?;
{
let (num_vars,) = if template_name == "scalar" {
(0,)
} else {
let template = table_templates.get(template_name).ok_or_else(|| {
log::error!(
"LUT table template is used but not defined: {}",
template_name
);
let msg = format!(
"LUT table template is used but not defined: {}",
template_name
);
LibertyErr::Other(msg)
})?;
let num_vars = if template.var2.is_some() { 2 } else { 1 };
let var_names = [Some(template.var1.as_str()), template.var2.as_deref()];
let output_cap_var = var_names
.iter()
.find_position(|&&name| name == Some(required_var1_name))
.map(|(pos, _)| pos);
let input_transition_var = var_names
.iter()
.find_position(|&&name| name == Some(required_var2_name))
.map(|(pos, _)| pos);
match (output_cap_var, input_transition_var) {
(Some(0), Some(1)) => {
debug_assert_eq!(num_vars, 2);
} (Some(1), _) | (_, Some(0)) => {
debug_assert!(num_vars > 0);
interp = interp.swap_variables();
}
_ => {
debug_assert_eq!(num_vars, 0);
} }
(num_vars,)
};
let actual_num_vars = match &interp {
Interp::Scalar(_) => 0,
Interp::Interp1D1(_) | Interp::Interp1D2(_) => 1,
Interp::Interp2D(_) => 2,
};
if num_vars != actual_num_vars {
log::error!(
"Table dimension mismatch. Template '{}' defines {} variables but found {}.",
template_name,
num_vars,
actual_num_vars
);
return Err(LibertyErr::TableMalformed);
}
}
Ok(interp)
}
impl<'a> LibertyTimingLibrary<'a> {
pub fn new(lib: &'a Group) -> Result<Self, LibertyErr> {
if lib.name != "library" {
Err(LibertyErr::Other(format!(
"Group must be a `library` group but it is `{}`",
lib.name
)))?;
}
let mut l = Self {
lib,
table_templates: HashMap::default(),
cells: Default::default(),
time_unit: Default::default(),
capacitive_load_unit: Default::default(),
};
l.init_units()?;
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(),
))?;
}
l.table_templates = read_template_tables(lib)?;
for cell_group in lib.find_groups_by_name("cell") {
let cell_name = get_cell_name(cell_group)?;
let mut cell = Cell::default();
for pin_group in cell_group.find_groups_by_name("pin") {
let pin_name = pin_group
.arguments
.first()
.and_then(|v| v.as_str())
.ok_or_else(|| {
let msg =
format!("Pin group in cell `{}` has no name argument.", cell_name);
log::error!("{}", msg);
LibertyErr::Other(msg)
})?;
let capacitance = pin_group
.get_simple_attribute("capacitance")
.and_then(|v| v.as_float())
.unwrap_or_default()
* l.capacitive_load_unit;
let mut pin = Pin {
capacitance,
..Default::default()
};
for timing_group in pin_group.find_groups_by_name("timing") {
let related_pin = timing_group
.get_simple_attribute("related_pin")
.and_then(|v| v.as_str())
.ok_or_else(|| {
log::error!(
"Timing group has no related pin in cell `{}`, pin `{}`.",
cell_name,
pin_name
);
LibertyErr::AttributeNotFound("related_pin".to_string())
})?;
let timing_type = timing_group
.get_simple_attribute("timing_type")
.and_then(|v| v.as_str())
.unwrap_or("combinational"); let is_delay_arc = matches!(
timing_type,
"combinational"
| "combinational_rise"
| "combinational_fall"
| "rising_edge"
| "falling_edge"
);
let is_constraint_arc = matches!(
timing_type,
"hold_rising" | "hold_falling" | "setup_rising" | "setup_falling"
);
if is_delay_arc {
let mut delay_arc = DelayArc::default();
match timing_type {
"combinational" => {
delay_arc.falling_edge = true;
delay_arc.rising_edge = true;
}
"rising_edge" | "combinational_rise" => delay_arc.rising_edge = true,
"falling_edge" | "combinational_fall" => delay_arc.falling_edge = true,
t => log::warn!("Unexpected timing_type: {}", t),
};
for table_group in &timing_group.groups {
let required_var1_name = "total_output_net_capacitance";
let required_var2_name = "input_net_transition";
let interp = get_interpolated_timing_table(
timing_group,
table_group,
&l.table_templates,
required_var1_name,
required_var2_name,
cell_name,
pin_name,
)?;
let interp = interp
.map_values(|&t| t * l.time_unit)
.map_x_axis(|cap| cap * l.capacitive_load_unit)
.map_y_axis(|t| t * l.time_unit);
match table_group.name.as_str() {
"rise_transition" => delay_arc.rise_transition = Some(interp),
"fall_transition" => delay_arc.fall_transition = Some(interp),
"cell_rise" => delay_arc.cell_rise = Some(interp),
"cell_fall" => delay_arc.cell_fall = Some(interp),
n => {
log::warn!("Unsupported timing table: '{}'", n);
}
}
}
pin.delay_arcs.insert(related_pin.to_string(), delay_arc);
}
if is_constraint_arc {
let mut constraint_arc = ConstraintArc::default();
for table_group in &timing_group.groups {
let required_var1_name = "related_pin_transition";
let required_var2_name = "constrained_pin_transition";
let interp = get_interpolated_timing_table(
timing_group,
table_group,
&l.table_templates,
required_var1_name,
required_var2_name,
cell_name,
pin_name,
)?;
let interp = interp
.map_values(|&t| t * l.time_unit)
.map_x_axis(|t| t * l.time_unit)
.map_y_axis(|t| t * l.time_unit);
match table_group.name.as_str() {
"rise_constraint" => constraint_arc.rise_constraint = Some(interp),
"fall_constraint" => constraint_arc.fall_constraint = Some(interp),
n => {
log::warn!("Unsupported timing table: '{}'", n);
}
}
}
match timing_type {
"hold_rising" => &mut pin.hold_rising,
"hold_falling" => &mut pin.hold_falling,
"setup_rising" => &mut pin.setup_rising,
"setup_falling" => &mut pin.setup_falling,
_ => unreachable!(
"Unexpected timing type for constraint arc: '{}'",
timing_type
),
}
.insert(related_pin.to_string(), constraint_arc);
}
}
cell.pins.insert(pin_name.to_string(), pin);
}
l.cells.insert(cell_name.to_string(), cell);
}
Ok(l)
}
fn init_units(&mut self) -> Result<(), LibertyErr> {
{
let time_unit_str = self
.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 = self
.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(())
}
pub fn get_pin(&self, cell: &str, output_pin: &str) -> Option<&Pin> {
self.cells.get(cell).and_then(|c| c.pins.get(output_pin))
}
pub(crate) fn get_delay_arc(
&self,
cell: &str,
output_pin: &str,
related_pin: &str,
) -> Option<&DelayArc> {
self.get_pin(cell, output_pin)
.and_then(|p| p.delay_arcs.get(related_pin))
}
pub(crate) fn get_hold_rising_arc(
&self,
cell: &str,
output_pin: &str,
related_pin: &str,
) -> Option<&ConstraintArc> {
self.get_pin(cell, output_pin)
.and_then(|p| p.hold_rising.get(related_pin))
}
pub(crate) fn get_hold_falling_arc(
&self,
cell: &str,
output_pin: &str,
related_pin: &str,
) -> Option<&ConstraintArc> {
self.get_pin(cell, output_pin)
.and_then(|p| p.hold_falling.get(related_pin))
}
pub(crate) fn get_setup_rising_arc(
&self,
cell: &str,
output_pin: &str,
related_pin: &str,
) -> Option<&ConstraintArc> {
self.get_pin(cell, output_pin)
.and_then(|p| p.setup_rising.get(related_pin))
}
pub(crate) fn get_setup_falling_arc(
&self,
cell: &str,
output_pin: &str,
related_pin: &str,
) -> Option<&ConstraintArc> {
self.get_pin(cell, output_pin)
.and_then(|p| p.setup_falling.get(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<'a> TimingLibrary for LibertyTimingLibrary<'a> {
fn get_slew(
&self,
edge_polarity: EdgePolarity,
cell: &str,
output_pin: &str,
related_pin: &str,
input_slew: Time,
output_capacitance: Capacitance,
) -> Option<Time> {
self.get_delay_arc(cell, output_pin, related_pin)
.and_then(|d| match edge_polarity {
EdgePolarity::Rise => d.rise_transition.as_ref(),
EdgePolarity::Fall => d.fall_transition.as_ref(),
})
.map(|f| {
f.eval2d((output_capacitance, input_slew))
})
}
fn get_cell_delay(
&self,
edge_polarity: EdgePolarity,
cell: &str,
output_pin: &str,
related_pin: &str,
input_slew: Time,
output_capacitance: Capacitance,
) -> Option<Time> {
self.get_delay_arc(cell, output_pin, related_pin)
.and_then(|d| match edge_polarity {
EdgePolarity::Rise => d.cell_rise.as_ref(),
EdgePolarity::Fall => d.cell_fall.as_ref(),
})
.map(|f| f.eval2d((output_capacitance, input_slew)))
}
fn get_hold_constraint(
&self,
cell: &str,
constrained_pin: &str,
related_pin: &str,
constrained_edge_polarity: EdgePolarity,
related_edge_polarity: EdgePolarity,
related_pin_transition: Time,
constrained_pin_transition: Time,
output_load: Capacitance,
) -> Option<Time> {
self.get_pin(cell, constrained_pin)
.and_then(|p| match related_edge_polarity {
EdgePolarity::Rise => p.hold_rising.get(related_pin),
EdgePolarity::Fall => p.hold_falling.get(related_pin),
})
.and_then(|arc| match constrained_edge_polarity {
EdgePolarity::Rise => arc.rise_constraint.as_ref(),
EdgePolarity::Fall => arc.fall_constraint.as_ref(),
})
.map(|f| f.eval2d((related_pin_transition, constrained_pin_transition)))
}
fn get_setup_constraint(
&self,
cell: &str,
constrained_pin: &str,
related_pin: &str,
constrained_edge_polarity: EdgePolarity,
related_edge_polarity: EdgePolarity,
related_pin_transition: Time,
constrained_pin_transition: Time,
output_load: Capacitance,
) -> Option<Time> {
self.get_pin(cell, constrained_pin)
.and_then(|p| match related_edge_polarity {
EdgePolarity::Rise => p.setup_rising.get(related_pin),
EdgePolarity::Fall => p.setup_falling.get(related_pin),
})
.and_then(|arc| match constrained_edge_polarity {
EdgePolarity::Rise => arc.rise_constraint.as_ref(),
EdgePolarity::Fall => arc.fall_constraint.as_ref(),
})
.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 timing_library = LibertyTimingLibrary::new(&library).unwrap();
}
#[test]
fn test_lut_variable_ordering() {
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 timing_library = LibertyTimingLibrary::new(&library).unwrap();
let arc_a_y = timing_library.get_delay_arc("INVX1", "Y", "A").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)); }