///|
pub(all) struct TimingArc {
related_pins : Array[String]
timing_type : String?
timing_sense : String?
condition : String?
tables : Array[Table]
position : Position
} derive(Debug)
///|
pub(all) struct Pin {
name : String
direction : String?
capacitance : Double?
max_capacitance : Double?
max_transition : Double?
function : String?
timings : Array[TimingArc]
source : Group
} derive(Debug)
///|
pub(all) struct Cell {
name : String
area : Double?
pins : Array[Pin]
source : Group
} derive(Debug)
///|
pub(all) struct Library {
name : String
units : Units?
cells : Array[Cell]
templates : Array[Group]
findings : Array[Finding]
source : Group
} derive(Debug)
///|
pub(all) struct Inventory {
cells : Int32
pins : Int32
arcs : Int32
tables : Int32
errors : Int32
warnings : Int32
} derive(Eq, Debug)
///|
/// Bounds expansion of shared pin declarations and repeated validation work.
pub(all) struct ModelLimits {
max_pins : Int32
max_work : Int32
} derive(Eq, Debug)
///|
pub extend ModelLimits with Eq::{equal, not_equal}
///|
pub extend ModelLimits with @debug.Debug::{to_repr}
///|
pub fn ModelLimits::default() -> ModelLimits {
{ max_pins: 100000, max_work: 4000000, }
}
///|
fn model_work(
timings : Array[TimingArc],
function : String?,
source : Group,
limit : Int32,
pos : Position,
) -> Int32 raise LibertyError {
let mut work = 1 +
source.items.length() +
function.map(s => s.length()).unwrap_or(0)
for item in source.items {
if item is Section(timing) && timing.kind == "timing" {
work += timing.items.length()
for node in timing.items {
if node is Section(table) && timing_table_kind(table.kind) {
work += table.items.length()
if work > limit {
raise failure(
"ModelLimit", "expanded validation work exceeds budget", pos,
)
}
}
}
}
}
for arc in timings {
work += 1 +
arc.related_pins.length() +
arc.condition.map(s => s.length()).unwrap_or(0)
for table in arc.tables {
work += 1 +
table.index_1.length() +
table.index_2.length() +
table.values.length()
if work > limit {
raise failure(
"ModelLimit", "expanded validation work exceeds budget", pos,
)
}
}
}
if work > limit {
raise failure("ModelLimit", "expanded validation work exceeds budget", pos)
}
work
}
///|
fn finding(
error : LibertyError,
path : String,
severity~ : Severity = Error,
) -> Finding {
let LibertyError(code, message, position) = error
{ code, severity, path, message, position, }
}
///|
fn simple_position(group : Group, key : String) -> Position {
for item in group.items {
if item is Attribute(name, _, pos) && name == key {
return pos
}
}
group.position
}
///|
fn optional_number(
group : Group,
key : String,
path : String,
findings : Array[Finding],
) -> Double? {
match group.text(key) {
None => None
Some(text) => {
let value = finite_number(text, position=simple_position(group, key)) catch {
error => {
findings.push(finding(error, path + "/" + key))
return None
}
}
Some(value)
}
}
}
///|
fn words(value : String) -> Array[String] {
let result = []
let mut start = 0
for i = 0; i <= value.length(); i = i + 1 {
if i == value.length() || whitespace(value[i]) {
if i > start {
result.push(value[start:i].to_owned())
}
start = i + 1
}
}
result
}
///|
fn timing_table_kind(kind : String) -> Bool {
[
"cell_rise", "cell_fall", "rise_transition", "fall_transition", "rise_constraint",
"fall_constraint",
].contains(kind)
}
///|
pub fn build_library(
root : Group,
model_limits~ : ModelLimits = ModelLimits::default(),
) -> Library raise LibertyError {
if root.kind != "library" || root.name().is_empty() {
raise failure(
"InvalidRoot",
"typed model needs one named library",
root.position,
)
}
if model_limits.max_pins < 1 ||
model_limits.max_pins > 1000000 ||
model_limits.max_work < 1 ||
model_limits.max_work > 16000000 {
raise failure(
"InvalidLimit",
"model limits require 1..1000000 pins and 1..16000000 work units",
root.position,
)
}
let mut expanded_pins = 0
let mut expanded_work = 0
let findings = []
let units = Some(read_units(root)) catch {
error => {
findings.push(finding(error, root.name(), severity=Warning))
None
}
}
let templates = root.groups("lu_table_template")
let by_name : Map[String, Group] = Map([])
for template in templates {
if by_name.contains(template.name()) {
findings.push({
code: "DuplicateTemplate",
severity: Error,
path: root.name() + "/" + template.name(),
message: "lookup template name is duplicated",
position: template.position,
})
} else {
by_name[template.name()] = template
}
}
let cells = []
for cell in root.groups("cell") {
let cell_path = root.name() + "/cell:" + cell.name()
let pins = []
for container in cell.groups("bus") + cell.groups("bundle") {
add_finding(
findings,
"UnexpandedPinContainer",
cell_path + "/" + container.kind + ":" + container.name(),
"bus/bundle data is retained; typed inventory and SI queries cover directly declared scalar pins only",
container.position,
severity=Warning,
)
}
for pin in cell.groups("pin") {
let pin_path = cell_path + "/pin:" + pin.name()
let timings = []
for timing in pin.groups("timing") {
let tables = []
for node in timing.items {
if node is Section(group) && timing_table_kind(group.kind) {
let table = read_table(group, template?=by_name.get(group.name())) catch {
error => {
let LibertyError(code, _, _) = error
findings.push(
finding(
error,
pin_path + "/" + group.kind,
severity=if code == "UnsupportedDimension" {
Warning
} else {
Error
},
),
)
continue
}
}
tables.push(table)
}
}
timings.push({
related_pins: match timing.text("related_pin") {
Some(value) => words(value)
None => []
},
timing_type: timing.text("timing_type"),
timing_sense: timing.text("timing_sense"),
condition: timing.text("when"),
tables,
position: timing.position,
})
}
let names = if !pin.arguments.is_empty() &&
pin.arguments.all(args => {
args.length() == 1 && !atom_text(args[0]).is_empty()
}) {
pin.arguments.map(args => atom_text(args[0]))
} else {
[""]
}
let capacitance = optional_number(pin, "capacitance", pin_path, findings)
let max_capacitance = optional_number(
pin, "max_capacitance", pin_path, findings,
)
let max_transition = optional_number(
pin, "max_transition", pin_path, findings,
)
let function = pin.text("function")
let cost = model_work(
timings,
function,
pin,
model_limits.max_work,
pin.position,
)
for name in names {
if expanded_pins >= model_limits.max_pins ||
cost > model_limits.max_work - expanded_work {
raise failure(
"ModelLimit",
"expanded pin count or validation work exceeds budget",
pin.position,
)
}
expanded_pins += 1
expanded_work += cost
pins.push({
name,
direction: pin.text("direction"),
capacitance,
max_capacitance,
max_transition,
function,
timings,
source: pin,
})
}
}
cells.push({
name: cell.name(),
area: optional_number(cell, "area", cell_path, findings),
pins,
source: cell,
})
}
{ name: root.name(), units, cells, templates, findings, source: root, }
}
///|
pub fn load_library(
text : String,
limits~ : Limits = Limits::default(),
model_limits~ : ModelLimits = ModelLimits::default(),
) -> Library raise LibertyError {
build_library(parse_liberty(text, limits~), model_limits~)
}
///|
pub fn Library::cell(self : Library, name : String) -> Cell? {
self.cells.iter().find_first(cell => cell.name == name)
}
///|
pub fn Cell::pin(self : Cell, name : String) -> Pin? {
self.pins.iter().find_first(pin => pin.name == name)
}
///|
pub fn Library::inventory(self : Library) -> Inventory {
let mut pins = 0
let mut arcs = 0
let mut tables = 0
for cell in self.cells {
pins += cell.pins.length()
for pin in cell.pins {
arcs += pin.timings.length()
for timing in pin.timings {
tables += timing.tables.length()
}
}
}
{
cells: self.cells.length(),
pins,
arcs,
tables,
errors: self.findings.filter(f => f.severity == Error).length(),
warnings: self.findings.filter(f => f.severity == Warning).length(),
}
}
///|
fn nullable_number(value : Double?) -> Json {
match value {
Some(number) => Json(number)
None => Json::null()
}
}
///|
fn nullable_text(value : String?) -> Json {
match value {
Some(text) => Json(text)
None => Json::null()
}
}
///|
pub fn inventory_json(library : Library) -> Json {
let inventory = library.inventory()
{
"schema": "moonliberty.inventory.v1",
"library": Json(library.name),
"delay_model": nullable_text(library.source.text("delay_model")),
"cells": Json(inventory.cells),
"pins": Json(inventory.pins),
"arcs": Json(inventory.arcs),
"tables": Json(inventory.tables),
"model_errors": Json(inventory.errors),
"model_warnings": Json(inventory.warnings),
"time_unit_seconds": nullable_number(
library.units.map(units => units.time_seconds),
),
"capacitance_unit_farads": nullable_number(
library.units.map(units => units.capacitance_farads),
),
"cell_names": Json(library.cells.map(cell => cell.name)),
}
}