Skip to main content

klayout_core/
bus.rs

1//! Bus-pin name parsing and expansion.
2//!
3//! LEF / DEF / Liberty all use `A[3:0]` (or `A<3:0>`) to declare a
4//! 4-bit bus pin. The data model here is scalar — each bit becomes
5//! its own `Pin` / `Net` / `Port`. This module provides:
6//!
7//! * [`parse_bus`] — `"A[3:0]"` → `BusName { base, lo, hi, ascending }`.
8//! * [`BusName::expand`] — yield the scalar names `A[3]`, `A[2]`, …
9//! * [`format_bus_pin`] — given base + index, produce `A[3]`.
10//! * [`Bus`] — captured original bus declaration so emitters can
11//!   round-trip the bracketed form back to LEF / DEF / Liberty.
12//!
13//! Bracket convention defaults to `[` / `]`; alternates are accepted
14//! via [`BusBitChars`] (`<>`, `()`, `{}`).
15
16use smol_str::SmolStr;
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub struct BusBitChars {
20    pub open: char,
21    pub close: char,
22}
23
24impl Default for BusBitChars {
25    fn default() -> Self {
26        Self {
27            open: '[',
28            close: ']',
29        }
30    }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct BusName {
35    pub base: SmolStr,
36    pub lo: i32,
37    pub hi: i32,
38    /// `true` if the original notation went lo:hi (e.g. `A[0:3]`),
39    /// `false` for descending (e.g. `A[3:0]`). Round-trip emission
40    /// preserves the original direction.
41    pub ascending: bool,
42}
43
44impl BusName {
45    /// Iterate scalar pin names in the original direction.
46    pub fn expand(&self, chars: BusBitChars) -> impl Iterator<Item = String> + '_ {
47        let (start, step): (i32, i32) = if self.ascending {
48            (self.lo, 1)
49        } else {
50            (self.hi, -1)
51        };
52        let count = (self.hi - self.lo).unsigned_abs() as usize + 1;
53        (0..count).map(move |k| {
54            let i = start + (k as i32) * step;
55            format!("{}{}{i}{}", self.base, chars.open, chars.close)
56        })
57    }
58
59    pub fn width(&self) -> u32 {
60        (self.hi - self.lo).unsigned_abs() + 1
61    }
62}
63
64/// Parse a bus name. Returns `None` if `s` doesn't contain bracket
65/// notation; the caller should treat that as a scalar pin.
66pub fn parse_bus(s: &str, chars: BusBitChars) -> Option<BusName> {
67    let open = s.find(chars.open)?;
68    let close = s.find(chars.close)?;
69    if open >= close {
70        return None;
71    }
72    let base = SmolStr::from(s[..open].trim());
73    let inside = &s[open + 1..close];
74    let parts: Vec<&str> = inside.split(':').collect();
75    let (a, b) = match parts.as_slice() {
76        [single] => {
77            // Scalar bit reference like `A[3]` — lo=hi=3, ascending true.
78            let n: i32 = single.trim().parse().ok()?;
79            (n, n)
80        }
81        [a, b] => {
82            let a: i32 = a.trim().parse().ok()?;
83            let b: i32 = b.trim().parse().ok()?;
84            (a, b)
85        }
86        _ => return None,
87    };
88    let lo = a.min(b);
89    let hi = a.max(b);
90    let ascending = a <= b;
91    Some(BusName {
92        base,
93        lo,
94        hi,
95        ascending,
96    })
97}
98
99/// Format a single bit of a bus: `(base, index)` → `base[index]`.
100pub fn format_bus_pin(base: &str, index: i32, chars: BusBitChars) -> String {
101    format!("{base}{}{index}{}", chars.open, chars.close)
102}
103
104/// One bus declaration captured during LEF/DEF/Liberty parsing.
105/// Emitters re-render the bracket form to round-trip cleanly.
106#[derive(Clone, Debug)]
107pub struct Bus {
108    pub name: BusName,
109    pub member_pins: Vec<SmolStr>,
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn parse_descending_range() {
118        let b = parse_bus("DATA[3:0]", BusBitChars::default()).unwrap();
119        assert_eq!(b.base.as_str(), "DATA");
120        assert_eq!(b.lo, 0);
121        assert_eq!(b.hi, 3);
122        assert!(!b.ascending);
123        assert_eq!(b.width(), 4);
124    }
125
126    #[test]
127    fn parse_ascending_range() {
128        let b = parse_bus("Q[0:7]", BusBitChars::default()).unwrap();
129        assert!(b.ascending);
130        assert_eq!(b.width(), 8);
131    }
132
133    #[test]
134    fn parse_single_bit() {
135        let b = parse_bus("A[5]", BusBitChars::default()).unwrap();
136        assert_eq!(b.lo, 5);
137        assert_eq!(b.hi, 5);
138        assert_eq!(b.width(), 1);
139    }
140
141    #[test]
142    fn parse_returns_none_for_scalar() {
143        assert!(parse_bus("scalar_pin", BusBitChars::default()).is_none());
144    }
145
146    #[test]
147    fn alternate_bit_chars() {
148        let chars = BusBitChars {
149            open: '<',
150            close: '>',
151        };
152        let b = parse_bus("D<7:0>", chars).unwrap();
153        assert_eq!(b.width(), 8);
154        assert_eq!(b.base.as_str(), "D");
155    }
156
157    #[test]
158    fn expand_yields_scalar_names() {
159        let b = parse_bus("A[3:0]", BusBitChars::default()).unwrap();
160        let names: Vec<String> = b.expand(BusBitChars::default()).collect();
161        assert_eq!(names, vec!["A[3]", "A[2]", "A[1]", "A[0]"]);
162    }
163
164    #[test]
165    fn ascending_expand_preserves_direction() {
166        let b = parse_bus("A[0:3]", BusBitChars::default()).unwrap();
167        let names: Vec<String> = b.expand(BusBitChars::default()).collect();
168        assert_eq!(names, vec!["A[0]", "A[1]", "A[2]", "A[3]"]);
169    }
170
171    #[test]
172    fn format_bus_pin_uses_chosen_chars() {
173        let s = format_bus_pin("VAL", 42, BusBitChars { open: '<', close: '>' });
174        assert_eq!(s, "VAL<42>");
175    }
176}