LibertyScope is a Python-based tool for inspecting Liberty (.lib) standard-cell libraries and computing cell propagation delay and output slew from NLDM lookup tables. The project includes a graphical interface for browsing cells, checking timing arcs, computing individual cells, and building simple timing paths.
The current version keeps the parsing and calculation layers separated from the GUI. The GUI is now a Python package, divided into multiple files by responsibility, and the Path Builder supports stages with more than one selected input pin.
- Load and inspect Liberty
.libfiles. - Browse standard cells, input pins, output pins, pin capacitances, and timing arcs.
- Inspect timing tables associated with each propagation arc:
cell_risecell_fallrise_transitionfall_transition
- Compute propagation delay (
tp) and output slew for a selected cell. - Perform bilinear interpolation over NLDM lookup tables.
- Clamp out-of-range slew/load values to the nearest LUT boundary.
- Build a timing path through multiple cells.
- Propagate output slew from one stage to the next.
- Estimate the load of each stage from the input capacitance of the following stage.
- Select multiple input pins in one Path Builder stage and keep the worst local timing arc.
- Run the GUI either as a module or through a launcher script.
- Python 3.10 or newer
liberty-parser- Tkinter, usually included with standard Python installations
Install the parser dependency with:
pip install liberty-parser==0.0.29On some Linux distributions, Tkinter may need to be installed separately. For example:
sudo apt install python3-tkFrom the project root directory:
python -m guiAlternatively:
python run_gui.pyYou can also run the basic test script:
python test.pyThe test script loads NangateOpenCellLibrary_typical.lib, computes delay/slew for selected cells, and prints a simple path delay example.
IEEE/
├── NangateOpenCellLibrary_typical.lib
├── test.py
├── run_gui.py
├── readme.md
├── README_CAMBIOS.md
│
├── gui/
│ ├── __init__.py
│ ├── __main__.py
│ ├── main.py
│ ├── app.py
│ ├── cells_tab.py
│ ├── detail_tab.py
│ └── path_tab.py
│
├── libertyscope/
│ ├── __init__.py
│ └── explorer.py
│
└── library/
├── __init__.py
├── library.py
└── cells/
├── __init__.py
├── cells.py
└── cell/
├── __init__.py
├── cell.py
├── pin.py
├── timingArc.py
├── lut.py
└── utils.py
The project is organized into three main layers.
flowchart TD
A[Liberty .lib file] --> B[liberty-parser]
B --> C[libertyscope]
C --> D[library data model]
D --> E[Cell, Pin, TimingArc, Lut]
E --> F[Delay and slew calculation]
E --> G[GUI package]
G --> H[Cells tab]
G --> I[Cell Detail tab]
G --> J[Path Builder tab]
This layer wraps the tree generated by liberty-parser and provides helper methods to navigate Liberty groups and attributes.
Main file:
libertyscope/explorer.py
Main responsibilities:
- Load a Liberty file.
- Navigate Liberty groups such as
library,cell,pin, andtiming. - Read attributes from Liberty groups.
- Provide a cleaner interface over the raw parser output.
Example:
from libertyscope import load_liberty
lib = load_liberty("NangateOpenCellLibrary_typical.lib")
cell = lib.find("cell", "INV_X1")
pin = cell.find("pin", "ZN")This layer does not compute timing values. It only exposes parsed data.
This layer converts the parsed Liberty data into Python objects that represent the library, cells, pins, timing arcs, and lookup tables.
Main files:
library/library.py
library/cells/cells.py
library/cells/cell/cell.py
library/cells/cell/pin.py
library/cells/cell/timingArc.py
library/cells/cell/lut.py
library/cells/cell/utils.py
Main responsibilities:
- Store global library metadata.
- Build a list of standard cells.
- Extract input and output pins.
- Extract propagation timing arcs.
- Store NLDM lookup tables.
- Compute timing values using LUT interpolation.
The GUI is now a package instead of a single monolithic gui.py file.
Main files:
gui/app.py
gui/cells_tab.py
gui/detail_tab.py
gui/path_tab.py
gui/main.py
gui/__main__.py
Main responsibilities:
- Load Liberty files through a graphical menu.
- Display all cells in a table.
- Display detailed information about a selected cell.
- Compute individual cell delay and slew.
- Build and evaluate simple paths through multiple cells.
- Allow multiple input pins per Path Builder stage.
The GUI can be launched with:
python -m guibecause the package includes gui/__main__.py.
Defined in:
library/library.py
Represents the complete Liberty file.
Important attributes:
| Attribute | Meaning |
|---|---|
name |
Library name |
delay_model |
Liberty delay model, usually table_lookup |
time_unit |
Time unit used by the library |
cap_load_unit |
Capacitive load unit |
nom_voltage |
Nominal voltage |
nom_temperature |
Nominal temperature |
cells |
Container of parsed standard cells |
Defined in:
library/cells/cell/cell.py
Represents one standard cell.
Important attributes:
| Attribute | Meaning |
|---|---|
name |
Cell name |
area |
Cell area |
input_pins |
Dictionary of input pins |
output_pins |
Dictionary of output pins |
timing_arcs |
List of propagation timing arcs |
tp |
Worst propagation delay after computation |
slew_out |
Worst output slew after computation |
worst_arc |
Timing arc that produced the largest delay |
Important method:
cell.compute(input_slew, output_load)This computes:
cell_risecell_fallrise_transitionfall_transitiontp = max(cell_rise, cell_fall)slew_out = max(rise_transition, fall_transition)
For a full cell computation, all propagation arcs of the cell are evaluated and the slowest one is kept.
Defined in:
library/cells/cell/pin.py
Represents a Liberty pin.
Important attributes:
| Attribute | Meaning |
|---|---|
name |
Pin name |
direction |
Pin direction, such as input or output |
capacitance |
Input capacitance, when available |
timing_arcs |
Timing arcs associated with output pins |
Defined in:
library/cells/cell/timingArc.py
Represents a timing relationship from one input pin to one output pin.
Important attributes:
| Attribute | Meaning |
|---|---|
related_pin |
Input pin that activates the timing arc |
output_pin |
Output pin affected by the arc |
timing_sense |
Timing sense, such as positive_unate, negative_unate, or non_unate |
timing_type |
Timing type, such as combinational, rising edge, or falling edge |
luts |
Dictionary of the four NLDM lookup tables |
Important methods:
arc.is_propagation()
arc.compute(input_slew, output_load)Only propagation arcs with all four required LUTs are used for delay/slew calculations.
Defined in:
library/cells/cell/lut.py
Represents a 2D NLDM lookup table.
Important attributes:
| Attribute | Meaning |
|---|---|
name |
LUT name, such as cell_rise |
template |
Liberty LUT template name |
index_1 |
Input slew axis |
index_2 |
Output load axis |
values |
Table values |
Important methods:
lut.lookup(input_slew, output_load)
lut.lookup_1d(output_load, input_slew=None)The main lookup method uses bilinear interpolation.
Each timing arc is evaluated using two inputs:
input_slew
output_load
These two values are used to interpolate each of the four LUTs:
cell_rise
cell_fall
rise_transition
fall_transition
For one timing arc:
tp = max(cell_rise, cell_fall)
slew_out = max(rise_transition, fall_transition)
For one complete cell calculation:
all propagation arcs are evaluated
worst_arc = arc with the largest tp
cell.tp = worst_arc.tp
cell.slew_out = worst_arc.slew_out
The Lut.lookup() method performs bilinear interpolation over the Liberty table axes.
The first axis is the input slew axis:
index_1
The second axis is the output load axis:
index_2
The algorithm:
- Clamp
input_slewandoutput_loadif they are outside the LUT range. - Find the two surrounding points in the input slew axis.
- Find the two surrounding points in the output load axis.
- Read the four neighboring table values.
- Interpolate between those four values.
- Return the interpolated result and metadata about clamping and bounds.
Conceptually:
output_load
y0 y1
input_slew x0 q00 q01
x1 q10 q11
The returned value is interpolated between q00, q01, q10, and q11.
Implemented in:
gui/cells_tab.py
This tab shows a table of standard cells. It includes information such as:
- Cell name
- Area
- Number of input pins
- Number of output pins
- Number of timing arcs
- Total input capacitance
Selecting a cell updates the Cell Detail tab.
Implemented in:
gui/detail_tab.py
This tab shows detailed information for the selected cell:
- General cell data
- Input and output pins
- Pin capacitances
- Timing arcs
- Timing sense
- Timing type
- Individual cell computation fields
It also allows computing a selected cell using a user-provided input slew and output load.
Implemented in:
gui/path_tab.py
This tab allows building a cell chain and estimating the accumulated path delay.
For each stage, the user selects:
- Cell
- One or more input pins
- Output pin
Multiple input pins can be selected using Ctrl or Shift.
The Path Builder computes the total path delay stage by stage.
flowchart TD
A[Start] --> B[Read initial slew]
B --> C[Read final load]
C --> D[For each path stage]
D --> E[Find selected cell]
E --> F[Find selected input-to-output timing arcs]
F --> G{Is there a next stage?}
G -- Yes --> H[Load = sum of selected input pin capacitances in next stage]
G -- No --> I[Load = final load]
H --> J[Evaluate all selected arcs]
I --> J
J --> K[Compute cell_rise, cell_fall, rise_transition, fall_transition]
K --> L[tp = max cell_rise/cell_fall]
K --> M[slew_out = max rise/fall transition]
L --> N[Keep arc with largest tp]
M --> N
N --> O[Add stage tp to total delay]
O --> P[Propagate slew_out to next stage]
P --> Q{More stages?}
Q -- Yes --> D
Q -- No --> R[Show total tp and final slew]
For a stage with one input pin, the stage evaluates only one timing arc:
A1 -> ZN
For a stage with multiple selected input pins, all corresponding arcs are evaluated:
A1 -> ZN
A2 -> ZN
B1 -> ZN
The stage keeps the worst local arc:
stage_tp = max(tp(A1 -> ZN), tp(A2 -> ZN), tp(B1 -> ZN))
The output slew associated with the worst local arc becomes the input slew of the next stage.
The total path delay is:
total_tp = stage_1_tp + stage_2_tp + stage_3_tp + ...
For every stage except the last one, the output load is estimated from the next stage.
If the next stage has one selected input pin:
output_load = capacitance(next_input_pin)
If the next stage has multiple selected input pins:
output_load = sum(capacitance(pin) for pin in next_selected_input_pins)
For the last stage, the user-provided final load is used:
output_load = final_load
This makes the Path Builder conservative and allows one output to be modeled as driving multiple selected inputs in the next stage.
from library.library import Library
lib = Library("NangateOpenCellLibrary_typical.lib")
cell = lib.findCell("INV_X1")
result = cell.compute(input_slew=0.05, output_load=10.0)
print(result["tp"])
print(result["slew_out"])
print(result["arc"])Example of a simple chain:
from library.library import Library
lib = Library("NangateOpenCellLibrary_typical.lib")
inv = lib.findCell("INV_X1")
and2 = lib.findCell("AND2_X1")
dff = lib.findCell("DFF_X1")
inv.compute(input_slew=0.04, output_load=and2.input_pins["A1"].capacitance)
and2.compute(input_slew=inv.slew_out, output_load=dff.input_pins["CK"].capacitance)
dff.compute(input_slew=and2.slew_out, output_load=0.0)
total_delay = inv.tp + and2.tp + dff.tp
print(total_delay)The latest refactor focused on two goals:
- Convert the GUI into a Python package.
- Allow the Path Builder to evaluate more than one input pin per stage.
Before the refactor, the GUI lived in a single gui.py file. It is now organized as:
gui/
├── __init__.py
├── __main__.py
├── main.py
├── app.py
├── cells_tab.py
├── detail_tab.py
└── path_tab.py
The old monolithic gui.py file was removed to avoid a name conflict with the new gui/ package.
The project can now be launched with:
python -m guior:
python run_gui.pyThe Path Builder now stores selected input pins as a list:
{
"cell": cell_name,
"input_pins": input_pins,
"output_pin": output_pin,
}For compatibility with the previous single-pin format, the helper _stage_input_pins() accepts both:
"input_pins"and:
"input_pin"The following packages were intentionally left unchanged:
libertyscope/
library/
The requested changes could be implemented from the GUI layer, so the parser wrapper and calculation engine were preserved.
The refactored code was checked with Python compilation:
python3 -m compileall -q .The GUI package import was also checked:
python3 - <<'PY'
from gui import LibertyGUI, main
print("gui package import ok")
PYA complete runtime test that loads a Liberty file requires liberty-parser to be installed in the environment.
- The current timing calculation is based on NLDM lookup tables.
- The Path Builder is a simplified timing-path estimator, not a complete STA engine.
- It does not perform full graph traversal, reconvergent path analysis, clock analysis, setup/hold checking, or constraint propagation.
- Sequential arcs are only considered when they pass the current propagation-arc filter.
- The GUI assumes that the selected input/output pins correspond to valid timing arcs in the Liberty file.
- Out-of-range LUT queries are clamped to the closest table boundary.
- The worst case for multiple selected input pins is selected locally per stage using the largest
tp.
- Install dependencies.
- Run the GUI with
python -m gui. - Open a Liberty
.libfile. - Inspect the Cells tab.
- Select a cell and check its pins/arcs in the Cell Detail tab.
- Test individual cell computation.
- Build a chain in the Path Builder.
- Select multiple input pins where needed.
- Compute the chain and inspect total delay and final slew.
For code changes, run:
python3 -m compileall -q .before packaging or submitting the project.
LibertyScope provides a compact educational and experimental environment for exploring Liberty files and understanding how standard-cell timing values are obtained from NLDM tables. Its structure separates parsing, timing calculation, and graphical interaction, which makes the project easier to maintain and extend.
The latest version improves maintainability by converting the GUI into a package and improves the Path Builder by supporting multiple input pins per stage with a conservative worst-case selection.