use std::collections::HashSet;
use indexmap::IndexMap;
use crate::{Coordinate, LefDefOptions, ModDef, PhysicalPin, Polygon};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PinDirection {
Input,
Output,
InOut,
}
impl PinDirection {
fn from_lef_token(token: &str) -> Option<Self> {
match token {
"INPUT" => Some(PinDirection::Input),
"OUTPUT" => Some(PinDirection::Output),
"INOUT" => Some(PinDirection::InOut),
_ => None,
}
}
}
#[derive(Default)]
struct ParsedPort {
direction: Option<PinDirection>,
pin_use: Option<String>,
bits: IndexMap<usize, Option<ParsedPinGeometry>>,
}
struct ParsedPinGeometry {
layer: String,
polygon_um: Vec<(f64, f64)>,
}
struct ParsedMacro {
name: String,
width_um: Option<f64>,
height_um: Option<f64>,
pins: IndexMap<String, ParsedPort>,
}
fn clean_lef_token(token: &str) -> &str {
token.trim_matches(&[';', '"'][..])
}
fn parse_pin_name(name: &str, open_char: char, close_char: char) -> (String, usize) {
if name.ends_with(close_char)
&& let Some(open_pos) = name.rfind(open_char)
&& open_pos + 1 < name.len()
{
let base = &name[..open_pos];
let index_str = &name[open_pos + 1..name.len() - 1];
if let Ok(index) = index_str.parse::<usize>() {
return (base.to_string(), index);
}
}
(name.to_string(), 0)
}
fn parse_lef_numbers(tokens: &[&str], line: &str) -> Vec<f64> {
tokens
.iter()
.map(|t| clean_lef_token(t))
.filter(|t| !t.is_empty())
.map(|t| {
t.parse::<f64>()
.unwrap_or_else(|_| panic!("Invalid LEF number '{t}' in line: '{line}'"))
})
.collect()
}
fn parse_lef_macros(
lef: &str,
open_char: char,
close_char: char,
skip_sections: &HashSet<String>,
valid_pin_layers: Option<&HashSet<String>>,
) -> Vec<ParsedMacro> {
let mut macros = Vec::new();
let mut current_macro: Option<ParsedMacro> = None;
let mut current_pin: Option<(String, usize)> = None;
let mut in_port = false;
let mut current_layer: Option<String> = None;
let mut in_skipped_section: Option<String> = None;
for raw_line in lef.lines() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let tokens: Vec<&str> = line.split_whitespace().collect();
if tokens.is_empty() {
continue;
}
let first_token = tokens[0].to_ascii_uppercase();
if let Some(section_name) = in_skipped_section.as_ref() {
if first_token == "END"
&& tokens.get(1).map(|t| t.to_ascii_uppercase()).as_deref()
== Some(section_name.as_str())
{
in_skipped_section = None;
}
continue;
}
if skip_sections.contains(&first_token) {
in_skipped_section = Some(first_token);
continue;
}
match first_token.as_str() {
"MACRO" => {
if let Some(m) = current_macro.take() {
macros.push(m);
}
let name = tokens
.get(1)
.map(|t| clean_lef_token(t).to_string())
.unwrap_or_else(|| panic!("Invalid LEF MACRO line: '{line}'"));
current_macro = Some(ParsedMacro {
name,
width_um: None,
height_um: None,
pins: IndexMap::new(),
});
current_pin = None;
in_port = false;
current_layer = None;
}
"PIN" => {
let pin_name = tokens
.get(1)
.map(|t| clean_lef_token(t).to_string())
.unwrap_or_else(|| panic!("Invalid LEF PIN line: '{line}'"));
let (base_name, bit_idx) = parse_pin_name(&pin_name, open_char, close_char);
let m = current_macro
.as_mut()
.unwrap_or_else(|| panic!("LEF pin '{pin_name}' defined outside of a MACRO"));
let entry = m
.pins
.entry(base_name.clone())
.or_insert_with(|| ParsedPort {
direction: None,
pin_use: None,
bits: IndexMap::new(),
});
if entry.bits.contains_key(&bit_idx) {
panic!("LEF pin '{}' repeats bit {}", base_name, bit_idx);
}
entry.bits.insert(bit_idx, None);
current_pin = Some((base_name, bit_idx));
in_port = false;
current_layer = None;
}
"PORT" => {
in_port = true;
current_layer = None;
}
"END" => {
if in_port && tokens.len() == 1 {
in_port = false;
current_layer = None;
}
}
"DIRECTION" => {
let m = current_macro
.as_mut()
.unwrap_or_else(|| panic!("LEF DIRECTION defined outside of a MACRO"));
let (base_name, _) = current_pin
.as_ref()
.unwrap_or_else(|| panic!("LEF DIRECTION defined outside of a PIN"));
if let Some(token) = tokens.get(1) {
let dir_token = clean_lef_token(token).to_ascii_uppercase();
if let Some(dir) = PinDirection::from_lef_token(dir_token.as_str()) {
let entry = m.pins.get_mut(base_name).unwrap();
match entry.direction {
Some(existing) => {
assert_eq!(
existing, dir,
"Mismatched pin directions for LEF pin '{}'",
base_name
);
}
None => entry.direction = Some(dir),
}
}
}
}
"USE" => {
let m = current_macro
.as_mut()
.unwrap_or_else(|| panic!("LEF USE defined outside of a MACRO"));
let (base_name, _) = current_pin
.as_ref()
.unwrap_or_else(|| panic!("LEF USE defined outside of a PIN"));
if let Some(token) = tokens.get(1) {
let pin_use = clean_lef_token(token).to_ascii_uppercase();
let entry = m.pins.get_mut(base_name).unwrap();
match entry.pin_use.as_deref() {
Some(existing) => {
assert_eq!(
existing, pin_use,
"Mismatched pin USE values for LEF pin '{}'",
base_name
);
}
None => entry.pin_use = Some(pin_use),
}
}
}
"LAYER" => {
if in_port {
let layer = tokens
.get(1)
.map(|t| clean_lef_token(t).to_string())
.unwrap_or_else(|| panic!("Invalid LEF LAYER line: '{line}'"));
current_layer = Some(layer);
}
}
"RECT" | "POLYGON" => {
if in_port {
let m = current_macro
.as_mut()
.unwrap_or_else(|| panic!("LEF {first_token} defined outside of a MACRO"));
let (base_name, bit_idx) = current_pin
.as_ref()
.unwrap_or_else(|| panic!("LEF {first_token} defined outside of a PIN"));
let entry = m.pins.get_mut(base_name).unwrap();
let slot = entry.bits.get_mut(bit_idx).unwrap_or_else(|| {
panic!(
"LEF {first_token} defined for unknown bit {} of pin '{}'",
bit_idx, base_name
)
});
if slot.is_some() {
continue;
}
let Some(layer) = current_layer.clone() else {
panic!("LEF {first_token} for pin '{}' missing LAYER", base_name);
};
if let Some(valid_layers) = valid_pin_layers
&& !valid_layers.contains(&layer)
{
continue;
}
let numbers = parse_lef_numbers(&tokens[1..], line);
let points = if first_token == "RECT" {
if numbers.len() != 4 {
panic!("Invalid LEF RECT line: '{line}'");
}
let x1 = numbers[0];
let y1 = numbers[1];
let x2 = numbers[2];
let y2 = numbers[3];
vec![(x1, y1), (x1, y2), (x2, y2), (x2, y1)]
} else {
if numbers.len() < 6 || !numbers.len().is_multiple_of(2) {
panic!("Invalid LEF POLYGON line: '{line}'");
}
let mut points = Vec::new();
for pair in numbers.chunks(2) {
points.push((pair[0], pair[1]));
}
points
};
*slot = Some(ParsedPinGeometry {
layer,
polygon_um: points,
});
}
}
"SIZE" => {
let m = current_macro
.as_mut()
.unwrap_or_else(|| panic!("LEF SIZE defined outside of a MACRO"));
let width_token = tokens
.get(1)
.map(|t| clean_lef_token(t))
.unwrap_or_else(|| panic!("Invalid LEF SIZE line: '{line}'"));
let by_token = tokens
.get(2)
.unwrap_or_else(|| panic!("Invalid LEF SIZE line: '{line}'"));
let height_token = tokens
.get(3)
.map(|t| clean_lef_token(t))
.unwrap_or_else(|| panic!("Invalid LEF SIZE line: '{line}'"));
assert_eq!(
by_token.to_ascii_uppercase(),
"BY",
"Invalid LEF SIZE line: '{line}'"
);
let width_um = width_token
.parse::<f64>()
.unwrap_or_else(|_| panic!("Invalid LEF size value: '{width_token}'"));
let height_um = height_token
.parse::<f64>()
.unwrap_or_else(|_| panic!("Invalid LEF size value: '{height_token}'"));
m.width_um = Some(width_um);
m.height_um = Some(height_um);
}
_ => {}
}
}
if let Some(m) = current_macro.take() {
macros.push(m);
}
macros
}
pub(crate) fn mod_defs_from_lef(lef: &str, opts: &LefDefOptions) -> Vec<ModDef> {
let (open_char, close_char) = opts.open_close_chars();
let skip_sections = opts
.skip_lef_sections
.iter()
.map(|name| name.to_ascii_uppercase())
.collect::<HashSet<_>>();
let skip_pin_uses = opts
.skip_pin_uses
.iter()
.map(|u| u.to_ascii_uppercase())
.collect::<HashSet<_>>();
let macros = parse_lef_macros(
lef,
open_char,
close_char,
&skip_sections,
opts.valid_pin_layers.as_ref(),
);
let mut mod_defs = Vec::new();
for m in macros {
let mod_def = ModDef::new(&m.name);
if let Some(width_um) = m.width_um
&& let Some(height_um) = m.height_um
{
let width = (width_um * opts.units_microns as f64).round() as i64;
let height = (height_um * opts.units_microns as f64).round() as i64;
mod_def.set_width_height(width, height);
}
for (name, parsed_port) in m.pins {
if opts.ignore_pin_names.contains(&name) {
continue;
}
let pin_use = parsed_port
.pin_use
.as_deref()
.unwrap_or("SIGNAL")
.to_ascii_uppercase();
if skip_pin_uses.contains(&pin_use) {
continue;
}
let max_bit = parsed_port
.bits
.keys()
.copied()
.max()
.unwrap_or_else(|| panic!("LEF pin '{}' has no bits", name));
for bit in 0..=max_bit {
if !parsed_port.bits.contains_key(&bit) {
panic!("LEF pin '{}' has non-contiguous bit indices", name);
}
}
let width = max_bit + 1;
let io = match parsed_port.direction.unwrap_or(PinDirection::InOut) {
PinDirection::Input => crate::IO::Input(width),
PinDirection::Output => crate::IO::Output(width),
PinDirection::InOut => crate::IO::InOut(width),
};
mod_def.add_port(&name, io);
for (bit, geom) in parsed_port.bits {
let Some(geom) = geom else {
continue;
};
let points = geom
.polygon_um
.iter()
.map(|(x, y)| Coordinate {
x: (x * opts.units_microns as f64).round() as i64,
y: (y * opts.units_microns as f64).round() as i64,
})
.collect::<Vec<_>>();
if points.len() < 3 {
panic!("LEF pin '{}' has an invalid shape", name);
}
let polygon = Polygon::new(points);
let center = polygon.bbox().center();
mod_def.place_pin(
&name,
bit,
PhysicalPin::from_translation(&geom.layer, polygon - center, center),
);
}
}
mod_defs.push(mod_def);
}
mod_defs
}