///|
/// Identifiers of implemented rule families, not an exhaustive manual inventory.
pub fn standard_rule_ids() -> Array[String] {
[
"group-parent", "group-arguments", "attribute-form", "attribute-type", "attribute-duplicate",
"template-axis", "template-reference", "table-shape", "table-order", "transition-nonnegative",
"bus-type", "bus-range", "bus-members", "state-arguments", "state-references",
"statetable-nodes", "statetable-rows", "statetable-tokens",
]
}
///|
fn std_parents(kind : String) -> Array[String]? {
if std_lvf_table(kind) {
return Some(["timing"])
}
match kind {
"cell"
| "lu_table_template"
| "power_lut_template"
| "operating_conditions"
| "wire_load" => Some(["library"])
"type" => Some(["library", "cell"])
"pin" =>
Some(["cell", "test_cell", "scaled_cell", "model", "bus", "bundle"])
"bus"
| "bundle"
| "pg_pin"
| "ff"
| "ff_bank"
| "latch"
| "latch_bank"
| "statetable" => Some(["cell", "test_cell", "scaled_cell", "model"])
"timing" | "internal_power" => Some(["pin", "bus", "bundle"])
"leakage_power" => Some(["cell"])
"cell_rise"
| "cell_fall"
| "rise_transition"
| "fall_transition"
| "rise_constraint"
| "fall_constraint" => Some(["timing"])
"rise_power" | "fall_power" => Some(["internal_power"])
_ => None
}
}
///|
// The three moment families introduced by the 2017.06 release notes.
// Recognition is deliberately enumerated; arbitrary ocv_* extensions are not validated as these tables.
fn std_lvf_table(kind : String) -> Bool {
for prefix in ["ocv_mean_shift_", "ocv_std_dev_", "ocv_skewness_"] {
for
suffix in [
"cell_rise", "cell_fall", "rise_transition", "fall_transition", "retaining_rise",
"retaining_fall", "retain_rise_slew", "retain_fall_slew", "rise_constraint",
"fall_constraint",
] {
if kind == prefix + suffix {
return true
}
}
}
false
}
///|
fn std_attr_type(kind : String, name : String) -> String? {
if ["ff", "ff_bank", "latch", "latch_bank"].contains(kind) &&
["clear_preset_var1", "clear_preset_var2"].contains(name) {
return Some("state-value")
}
if kind == "type" {
return match name {
"base_type" => Some("array")
"data_type" => Some("bit")
"bit_from" | "bit_to" | "bit_width" => Some("integer")
"downto" => Some("boolean")
_ => None
}
}
if kind == "cell" {
if name == "area" {
return Some("float")
}
if [
"dont_use", "dont_touch", "is_macro_cell", "is_pad", "is_filler_cell", "is_decap_cell",
"is_tap_cell", "is_clock_gating_cell", "always_on",
].contains(name) {
return Some("boolean")
}
}
if ["pin", "bus", "bundle"].contains(kind) {
if name == "direction" {
return Some("direction")
}
if [
"capacitance", "rise_capacitance", "fall_capacitance", "max_capacitance",
"min_capacitance", "max_transition", "min_transition", "max_fanout", "fanout_load",
].contains(name) {
return Some("float")
}
if [
"clock", "clock_gate_clock_pin", "clock_gate_enable_pin", "clock_gate_test_pin",
"clock_gate_out_pin", "is_pad", "is_pass_gate", "is_unbuffered", "always_on",
].contains(name) {
return Some("boolean")
}
if [
"function", "three_state", "bus_type", "related_power_pin", "related_ground_pin",
].contains(name) {
return Some("atom")
}
}
if kind == "timing" {
if name == "timing_sense" {
return Some("sense")
}
if name == "default_timing" {
return Some("boolean")
}
if [
"related_pin", "related_bus_pins", "related_output_pin", "when", "timing_type",
].contains(name) {
return Some("atom")
}
}
if ["lu_table_template", "power_lut_template"].contains(kind) &&
["variable_1", "variable_2", "variable_3"].contains(name) {
return Some("atom")
}
if ["ff", "ff_bank", "latch", "latch_bank"].contains(kind) &&
[
"clocked_on", "clocked_on_also", "next_state", "clear", "preset", "data_in",
"enable", "enable_also",
].contains(name) {
return Some("expression")
}
if kind == "statetable" && name == "table" {
return Some("string")
}
if kind == "library" &&
[
"time_unit", "voltage_unit", "current_unit", "leakage_power_unit", "pulling_resistance_unit",
"bus_naming_style",
].contains(name) {
return Some("string")
}
None
}
///|
fn std_value_valid(value_type : String, atoms : Array[Atom]) -> Bool {
if value_type == "expression" {
return !atoms.is_empty() && !atoms_text(atoms).is_empty()
}
guard atoms is [atom] else { return false }
let text = atom_text(atom)
match value_type {
"array" => text == "array"
"bit" => text == "bit"
"direction" => ["input", "output", "inout", "internal"].contains(text)
"sense" => ["positive_unate", "negative_unate", "non_unate"].contains(text)
"state-value" => ["L", "H", "N", "T", "X"].contains(text)
"atom" => !text.is_empty()
_ => attribute_value_valid(value_type, atoms)
}
}
///|
fn std_issue(
findings : Array[Finding],
code : String,
group : Group,
path : String,
message : String,
) -> Unit {
add_finding(findings, code, path, message, group.position)
}
///|
fn std_group_rules(
group : Group,
parent : String,
path : String,
findings : Array[Finding],
) -> Unit {
if std_parents(group.kind) is Some(parents) {
if !parents.contains(parent) {
std_issue(
findings, "StandardGroupParent", group, path, "group is not permitted in this parent by the implemented rule",
)
}
if [
"cell", "type", "lu_table_template", "power_lut_template", "bus", "bundle",
"pg_pin", "operating_conditions", "wire_load",
].contains(group.kind) &&
group.name().is_empty() {
std_issue(
findings, "StandardGroupArguments", group, path, "group needs one nonempty name",
)
}
if group.kind == "pin" &&
(
group.arguments.is_empty() ||
group.arguments.any(a => a.length() != 1 || atom_text(a[0]).is_empty())
) {
std_issue(
findings, "StandardGroupArguments", group, path, "pin needs one or more nonempty pin names",
)
}
duplicate_properties(group, path, findings)
}
for item in group.items {
match item {
Attribute(name, atoms, pos) =>
if std_attr_type(group.kind, name) is Some(value_type) &&
!std_value_valid(value_type, atoms) {
add_finding(
findings,
"StandardAttributeType",
path + "/" + name,
"expected " + value_type + " value",
pos,
)
}
Complex(name, _, pos) =>
if std_attr_type(group.kind, name) != None {
add_finding(
findings,
"StandardAttributeForm",
path + "/" + name,
"standard simple attribute must use name : value;",
pos,
)
}
_ => ()
}
}
}
///|
fn std_template(
group : Group,
path : String,
findings : Array[Finding],
) -> Unit {
let mut gap = false
let seen : Map[String, Bool] = Map([])
for axis in ["1", "2", "3"] {
let variable = group.text("variable_" + axis)
let numbers = vector(group, "index_" + axis) catch {
error => {
findings.push(finding(error, path + "/index_" + axis))
continue
}
}
match (variable, numbers) {
(None, None) => gap = true
(Some(name), Some(values)) => {
if gap || values.is_empty() || seen.contains(name) {
std_issue(
findings, "StandardTemplateAxis", group, path, "axes must be contiguous, nonempty and use distinct variables",
)
}
seen[name] = true
for i = 0; i < values.length(); i = i + 1 {
if values[i] < 0.0 || (i > 0 && values[i] <= values[i - 1]) {
std_issue(
findings,
"StandardTemplateAxis",
group,
path + "/index_" + axis,
"lookup breakpoints must be nonnegative and strictly increasing",
)
break
}
}
}
_ =>
std_issue(
findings, "StandardTemplateAxis", group, path, "each variable requires its corresponding index",
)
}
}
if group.text("variable_1") == None {
std_issue(
findings, "StandardTemplateAxis", group, path, "lookup template needs variable_1 and index_1",
)
}
}
///|
fn std_table(
group : Group,
templates : Map[String, Group],
path : String,
findings : Array[Finding],
) -> Unit {
let template_kind = if group.kind == "rise_power" ||
group.kind == "fall_power" {
"power_lut_template"
} else {
"lu_table_template"
}
let template = templates.get(template_kind + "/" + group.name())
if group.name() != "scalar" && template == None {
std_issue(
findings,
"StandardTemplateReference",
group,
path,
"missing " + template_kind + " named " + group.name(),
)
return
}
let data = read_table_data(group, template?) catch {
error => {
findings.push(finding(error, path))
return
}
}
let mut seen_values = false
for item in group.items {
if item is Complex(name, _, pos) {
if name == "values" {
seen_values = true
}
if ["index_1", "index_2", "index_3"].contains(name) && seen_values {
add_finding(
findings,
"StandardTableOrder",
path + "/" + name,
"local indexes must precede values",
pos,
)
}
}
}
if data.axes.any(axis => axis.any(value => value < 0.0)) {
std_issue(
findings, "StandardTableAxis", group, path, "NLDM/power breakpoints must be nonnegative",
)
}
if ["rise_transition", "fall_transition"].contains(group.kind) &&
data.rows.any(row => row.any(value => value < 0.0)) {
std_issue(
findings, "NegativeTransition", group, path, "transition table values must be nonnegative",
)
}
}
///|
fn std_integer(
group : Group,
name : String,
path : String,
findings : Array[Finding],
) -> Int32? {
guard group.attribute(name) is Some(atoms) &&
attribute_value_valid("integer", atoms) else {
return None
}
let value = finite_number(atoms_text(atoms)) catch {
error => {
findings.push(finding(error, path + "/" + name))
return None
}
}
if value < -1000000.0 || value > 1000000.0 {
std_issue(
findings,
"RuleResourceLimit",
group,
path + "/" + name,
"implemented bus/state integer budget is +/-1000000; format validity is undetermined",
)
return None
}
Some(value.to_int())
}
///|
// Infer only when width, one endpoint and direction are explicitly supplied.
// Other optional-default combinations remain outside this implemented rule.
fn std_bus_endpoints(
from : Int32?,
to : Int32?,
width : Int32?,
direction : String?,
) -> (Int32, Int32)? {
if from is Some(from) && to is Some(to) {
return Some((from, to))
}
guard width is Some(width) && width > 0 else { return None }
let delta = match direction {
Some("true") => 1 - width
Some("false") => width - 1
_ => return None
}
match (from, to) {
(Some(from), None) => Some((from, from + delta))
(None, Some(to)) => Some((to - delta, to))
_ => None
}
}
///|
fn std_bus_type(
group : Group,
path : String,
findings : Array[Finding],
) -> Unit {
let width = std_integer(group, "bit_width", path, findings)
let from = std_integer(group, "bit_from", path, findings)
let to = std_integer(group, "bit_to", path, findings)
if width is Some(n) && n < 1 {
std_issue(
findings, "StandardBusRange", group, path, "bit_width must be positive",
)
}
if (from is Some(n) && n < 0) || (to is Some(n) && n < 0) {
std_issue(
findings, "StandardBusRange", group, path, "bus member numbers must be nonnegative",
)
}
if (from == None || to == None) &&
std_bus_endpoints(from, to, width, group.text("downto"))
is Some((first, last)) &&
(first < 0 || last < 0) {
std_issue(
findings, "StandardBusRange", group, path, "width and direction imply a negative bus endpoint",
)
}
if width is Some(width) && from is Some(from) && to is Some(to) {
let distance = if from > to { from - to } else { to - from }
if width != distance + 1 {
std_issue(
findings, "StandardBusRange", group, path, "bit_width differs from explicit endpoint distance",
)
}
}
if from is Some(from) &&
to is Some(to) &&
group.text("downto") is Some(order) &&
from != to {
if (order == "true" && from < to) || (order == "false" && from > to) {
std_issue(
findings, "StandardBusRange", group, path, "downto contradicts explicit endpoints",
)
}
}
}
///|
priv struct TypeScope {
library : Map[String, Group]
cell : Map[String, Group]
}
///|
fn std_bus(
group : Group,
types : TypeScope,
path : String,
findings : Array[Finding],
) -> Unit {
let name = group.text("bus_type").unwrap_or("")
let definition = match types.cell.get(name) {
Some(definition) => definition
None =>
match types.library.get(name) {
Some(definition) => definition
None => {
std_issue(
findings, "StandardBusType", group, path, "bus_type must reference a declared visible type",
)
return
}
}
}
let first = group.items.get(0)
if !(first is Some(Attribute("bus_type", _, _))) {
std_issue(
findings, "StandardBusType", group, path, "bus_type must be the first statement in bus",
)
}
let from = std_integer(definition, "bit_from", path, findings)
let to = std_integer(definition, "bit_to", path, findings)
let width = std_integer(definition, "bit_width", path, findings)
let endpoints = std_bus_endpoints(from, to, width, definition.text("downto"))
for pin in group.groups("pin") {
if group.text("direction") is Some(direction) &&
pin.text("direction") is Some(overridden) &&
direction != overridden {
std_issue(
findings, "StandardBusMember", pin, path, "bus members must share direction",
)
}
for argument in pin.arguments {
if argument is [atom] &&
atom_text(atom).split_once("[") is Some((base, tail)) &&
tail.has_suffix("]") {
if base != group.name() {
std_issue(
findings, "StandardBusMember", pin, path, "member prefix differs from bus name",
)
}
let range_text = tail[0:tail.length() - 1].to_owned()
let ends = range_text.split(":").map(v => v.to_owned()).to_array()
if ends.length() > 2 || ends.is_empty() {
std_issue(
findings, "StandardBusMember", pin, path, "member needs one number or a range",
)
continue
}
for end in ends {
if !attribute_value_valid("integer", [Word(end)]) {
std_issue(
findings, "StandardBusMember", pin, path, "member number must be an integer",
)
continue
}
let number = finite_number(end) catch {
_ => {
std_issue(
findings, "RuleResourceLimit", pin, path, "member number exceeds numeric budget",
)
continue
}
}
if number < 0.0 {
std_issue(
findings, "StandardBusMember", pin, path, "bus member numbers must be nonnegative",
)
continue
}
if endpoints is Some((from, to)) {
let low = if from < to { from } else { to }
let high = if from > to { from } else { to }
if number < low.to_double() || number > high.to_double() {
std_issue(
findings, "StandardBusMember", pin, path, "member is outside type endpoints",
)
}
}
}
}
}
}
}
///|
priv struct StdCellScope {
names : Map[String, Bool]
statetables : Int32
}
///|
fn std_cell_names(cell : Group) -> Map[String, Bool] {
let names : Map[String, Bool] = Map([])
for item in cell.items {
if item is Section(group) {
if ["pin", "bus", "bundle", "pg_pin"].contains(group.kind) {
for argument in group.arguments {
if argument is [atom] {
names[atom_text(atom)] = true
}
}
}
if ["ff", "latch", "ff_bank", "latch_bank"].contains(group.kind) {
for i = 0; i < 2 && i < group.arguments.length(); i = i + 1 {
if group.arguments[i] is [atom] {
names[atom_text(atom)] = true
}
}
}
if group.kind == "statetable" &&
declaration_argument(group.arguments, 1) is Some(states) {
for name in words(states) {
names[name] = true
}
}
}
}
names
}
///|
fn std_state(
group : Group,
cell : StdCellScope?,
path : String,
findings : Array[Finding],
) -> Unit {
if group.kind == "latch" || group.kind == "latch_bank" {
let data = group.attribute("data_in") != None
let enable = group.attribute("enable") != None
if data != enable ||
(group.attribute("enable_also") != None && !(data && enable)) {
std_issue(
findings, "StandardStateRequired", group, path, "latch data_in and enable must occur together; enable_also requires both",
)
}
let controls = group.attribute("clear") != None &&
group.attribute("preset") != None
let combined_value = group.attribute("clear_preset_var1") != None ||
group.attribute("clear_preset_var2") != None
if controls != combined_value {
std_issue(
findings, "StandardStateRequired", group, path, "latch clear/preset pair requires a combined state value, and conversely",
)
}
}
if group.kind == "ff" || group.kind == "ff_bank" {
if group.text("next_state").unwrap_or("").is_empty() {
std_issue(
findings,
"StandardStateRequired",
group,
path + "/next_state",
"ff and ff_bank require next_state",
)
}
if group.text("clocked_on").unwrap_or("").is_empty() &&
group.text("clocked_on_also").unwrap_or("").is_empty() {
std_issue(
findings, "StandardStateRequired", group, path, "flip-flop requires a clock equation",
)
}
}
let bank = group.kind == "ff_bank" || group.kind == "latch_bank"
let arity = if bank { 3 } else { 2 }
if group.arguments.length() != arity ||
declaration_argument(group.arguments, 0).unwrap_or("").is_empty() ||
declaration_argument(group.arguments, 1).unwrap_or("").is_empty() ||
declaration_argument(group.arguments, 0) ==
declaration_argument(group.arguments, 1) {
std_issue(
findings, "StandardStateArguments", group, path, "state group needs two distinct internal names and a width for bank groups",
)
}
if bank {
let width = declaration_argument(group.arguments, 2).unwrap_or("")
let positive = if attribute_value_valid("integer", [Word(width)]) {
(finite_number(width) catch { _ => 0.0 }) > 0.0
} else {
false
}
if !positive {
std_issue(
findings, "StandardStateArguments", group, path, "state bank width must be a positive integer",
)
}
}
if cell is Some(cell) {
let names = cell.names
for
key in [
"clocked_on", "clocked_on_also", "next_state", "clear", "preset", "data_in",
"enable", "enable_also",
] {
if group.text(key) is Some(expression) {
if bank || expression.contains(":") {
add_finding(
findings,
"UnsupportedVectorExpression",
path + "/" + key,
"vector/bank equation is preserved; scalar reference checks do not validate it",
simple_position(group, key),
severity=Warning,
)
continue
}
check_logic(
expression,
names,
path + "/" + key,
simple_position(group, key),
findings,
)
}
}
}
}
///|
fn std_statetable(
group : Group,
cell : StdCellScope?,
path : String,
findings : Array[Finding],
) -> Unit {
if group.arguments.length() != 2 ||
!(group.arguments.get(0) is Some([Text(_)])) ||
!(group.arguments.get(1) is Some([Text(_)])) {
std_issue(
findings, "StandardStateTableNodes", group, path, "statetable needs two quoted node lists",
)
return
}
let inputs = words(declaration_argument(group.arguments, 0).unwrap_or(""))
let states = words(declaration_argument(group.arguments, 1).unwrap_or(""))
let names : Map[String, Bool] = Map([])
for name in inputs + states {
if names.contains(name) {
std_issue(
findings, "StandardStateTableNodes", group, path, "statetable node names must be unique",
)
}
names[name] = true
}
if states.is_empty() {
std_issue(
findings, "StandardStateTableNodes", group, path, "statetable requires internal nodes",
)
}
if cell is Some(cell) {
let declared = cell.names
for name in inputs {
if !declared.contains(name) {
std_issue(
findings,
"UnknownSignal",
group,
path,
"statetable input is undeclared: " + name,
)
}
}
if cell.statetables > 1 {
std_issue(
findings, "StandardStateTableNodes", group, path, "cell may contain only one statetable",
)
}
}
let table = group.text("table").unwrap_or("")
if table.is_empty() {
std_issue(
findings, "StandardStateTableRows", group, path, "statetable requires nonempty table string",
)
return
}
for row in table.split(",") {
let parts = row.split(":").map(v => words(v.to_owned())).to_array()
if parts.length() != 3 ||
parts[0].length() != inputs.length() ||
parts[1].length() != states.length() ||
parts[2].length() != states.length() {
std_issue(
findings, "StandardStateTableRows", group, path, "row must match input, current-state and next-state node counts",
)
continue
}
for i = 0; i < 3; i = i + 1 {
let allowed = if i == 0 {
["L", "H", "-", "L/H", "H/L", "R", "F", "~R", "~F"]
} else if i == 1 {
["L", "H", "-", "L/H", "H/L"]
} else {
["L", "H", "-", "L/H", "H/L", "X", "N"]
}
if parts[i].any(token => !allowed.contains(token)) {
std_issue(
findings, "StandardStateTableToken", group, path, "token is not valid in this statetable column",
)
}
}
}
}
///|
fn std_walk(
group : Group,
parent : String,
cell : StdCellScope?,
templates : Map[String, Group],
types : TypeScope,
path : String,
findings : Array[Finding],
) -> Unit {
std_group_rules(group, parent, path, findings)
// A type declared in a cell is local and shadows the library's type without
// changing the environment of sibling cells (Reference Manual 2-123).
let local_types = if group.kind == "cell" {
let declarations : Map[String, Group] = Map([])
for definition in group.groups("type") {
if declarations.contains(definition.name()) {
std_issue(
findings,
"DuplicateType",
definition,
path + "/type",
"cell-local bus type is duplicated",
)
} else {
declarations[definition.name()] = definition
}
}
{ library: types.library, cell: declarations, }
} else {
types
}
let scope = if ["cell", "test_cell", "scaled_cell", "model"].contains(
group.kind,
) {
Some({
names: std_cell_names(group),
statetables: group.groups("statetable").length(),
})
} else {
cell
}
match group.kind {
"lu_table_template" | "power_lut_template" =>
std_template(group, path, findings)
"type" => std_bus_type(group, path, findings)
"bus" => std_bus(group, local_types, path, findings)
"ff" | "ff_bank" | "latch" | "latch_bank" =>
std_state(group, scope, path, findings)
"statetable" => std_statetable(group, scope, path, findings)
_ =>
if timing_table_kind(group.kind) ||
std_lvf_table(group.kind) ||
["rise_power", "fall_power"].contains(group.kind) {
std_table(group, templates, path, findings)
}
}
for item in group.items {
if item is Section(child) {
std_walk(
child,
group.kind,
scope,
templates,
local_types,
path + "/" + child.kind + "(" + child.name() + ")",
findings,
)
}
}
}
///|
/// Apply documented, implemented 2017.06 rules. This is not complete conformance.
pub fn standard_findings(root : Group) -> Array[Finding] {
ignore(write_liberty(root)) catch {
error => return [finding(error, "/library")]
}
collect_standard_findings(root)
}
///|
fn collect_standard_findings(root : Group) -> Array[Finding] {
let findings = []
let templates : Map[String, Group] = Map([])
let types : Map[String, Group] = Map([])
for item in root.items {
if item is Section(group) {
if ["lu_table_template", "power_lut_template"].contains(group.kind) {
let key = group.kind + "/" + group.name()
if templates.contains(key) {
std_issue(
findings,
"DuplicateTemplate",
group,
"/library/" + key,
"template name is duplicated in its namespace",
)
} else {
templates[key] = group
}
}
if group.kind == "type" {
if types.contains(group.name()) {
std_issue(
findings, "DuplicateType", group, "/library/type", "bus type name is duplicated",
)
} else {
types[group.name()] = group
}
}
}
}
std_walk(
root,
"",
None,
templates,
{ library: types, cell: Map([]), },
"/library(" + root.name() + ")",
findings,
)
findings
}