A pure-Python parser for Liberty (.lib) cell-library files — the
industry-standard format used by EDA tools to describe the timing, power,
and interface of standard cells.
- Full Liberty grammar — comments, simple attributes, complex attributes, and arbitrarily deep group blocks
- Round-trip write —
write_liberty()reconstructs the file with the exact original whitespace and formatting - In-place editing —
set_val(),set_vals(),set_LU_matrix(),add_simple(),add_complex(),add_block(),remove_block(),merge_blocks() - Hierarchical navigation —
navigate()with exact-match or{RE}regexfilters,navigate_to_base()to walk up the tree - Convenience helpers —
get_cell_list(),get_pin_list(),map_LU_indexes() - Parse from string — no file required; pass a Liberty text block directly
- Zero required dependencies — only the Python standard library
- Optional colored output via
termcolor - CLI included:
liberty-parser cells.lib --cell INV_X1 --pins
pip install eda-liberty-parser # no extra dependencies
pip install "eda-liberty-parser[color]" # adds termcolor for colored outputOr for development:
git clone https://github.com/rohaansch/liberty-parser
cd liberty-parser
pip install -e ".[dev]"from liberty_parser import LP
lib = LP("cells.lib")
print(lib.get_cell_list())
# ['INV_X1', 'AND2_X1', 'DFFS_X2']
print(lib.get_pin_list(cell="INV_X1"))
# ['A', 'ZN']
# Read a library attribute
time_unit = lib.navigate_single('library', 'time_unit').get_val()
print(time_unit)
# 1nsnavigate() accepts a chain of filter strings, one per hierarchy level:
# All cells in the library
cells = lib.navigate('library', 'cell')
# Specific cell by name
inv = lib.navigate_single('library', 'cell:INV_X1')
# Pin direction of a specific pin
direction = lib.navigate_single('library', 'cell:INV_X1', 'pin:ZN', 'direction')
print(direction.get_val())
# output
# Regex filter — find all *_template blocks
for tmpl in lib.navigate('library', '{RE}_template$'):
print(tmpl.get_val())navigate_single() returns the first match (or None).
navigate_singles() is the fastest variant — stops at the first match at
every level.
All edits modify the in-memory dict and are written back with write_liberty():
# Change a simple attribute value
lib.navigate_single('library', 'time_unit').set_val('2ns')
# Or use the two-argument shorthand (nav_to, new_value)
lib.navigate_single('library').set_val('voltage_unit', '1.8V')
# Edit a lookup-table 2-D matrix
cell_rise = lib.navigate_single(
'library', 'cell:INV_X1', 'pin:ZN', 'timing', 'cell_rise'
)
matrix = cell_rise.get_LU_matrix()
matrix[0][0] = '0.15'
cell_rise.set_LU_matrix(matrix)
# Write back to disk
lib.write_liberty("cells_modified.lib")library = lib.navigate_single('library')
# Add a new simple attribute
library.add_simple('nom_voltage', '1.8')
# Add a new complex attribute
library.add_complex('operating_conditions', 'typical')
# Remove a cell
cell = lib.navigate_single('library', 'cell:INV_X1')
cell.remove_block()
# Merge another Liberty block into an existing one
lib.navigate_single('library').merge_blocks('extra_cells.lib')No file needed — pass Liberty text directly:
snippet = 'library (mini) { voltage_unit : "1V"; nom_voltage : 1.8; }'
mini = LP(snippet)
print(mini.navigate_single('library', 'nom_voltage').get_val())
# 1.8from liberty_parser import is_comment, is_simple_attribute, is_complex_attribute, is_block
for element in lib.navigate('library'):
if is_block(element):
print(f"Block: {element.get_val()}")
elif is_simple_attribute(element):
print(f"Attr: {element.get_val()}")| Argument | Behavior |
|---|---|
None |
Create empty LP object |
filepath (.lib file) |
Parse the file |
text (Liberty string) |
Parse the string as a Liberty block |
dict |
Wrap an existing internal dict |
LP |
Share the internal dict of another LP object |
| Method | Description |
|---|---|
navigate(*filters) |
Return all matching LP objects |
navigate_single(*filters) |
Return first match at last filter |
navigate_singles(*filters) |
Return first match at every filter (fastest) |
navigate_to_base() |
Walk up to the file root |
| Method | Description |
|---|---|
get_val(nav_to=None) |
Return value or name string |
get_vals(nav_to=None) |
Return comma-separated value as a list |
get_LU_matrix(nav_to=None) |
Return LU table as a 2-D list |
get_type() |
Return the element type string |
get_cell_list() |
Return all cell names |
get_pin_list(cell=None) |
Return pin names for one or all cells |
map_LU_indexes() |
Return transition/load index map |
| Method | Description |
|---|---|
set_val(nav_or_val, val=None) |
Set a simple or complex attribute value |
set_vals(nav_or_vals, vals=None) |
Replace comma-separated value list |
set_LU_matrix(nav_or_matrix, matrix=None) |
Replace LU table 2-D matrix |
| Method | Description |
|---|---|
add_simple(name, value, insert_ndx=0) |
Insert a simple attribute |
add_complex(type, name, insert_ndx=0) |
Insert a complex attribute |
add_block(lp_block, insert_ndx=0) |
Insert an LP element |
add_blocks(input, insert_ndx=0) |
Parse and insert multiple elements |
remove_block(ndx=None) |
Remove element at index, or remove self |
move_block(from_index, to_index) |
Reorder elements |
merge_blocks(blocks_input) |
Deep merge another Liberty block |
| Method | Description |
|---|---|
write_liberty(out_path) |
Write parsed dict back to a .lib file |
print(indent=0) |
Pretty-print the internal dict |
liberty-parser cells.lib
liberty-parser cells.lib --cells
liberty-parser cells.lib --cell INV_X1
liberty-parser cells.lib --cell INV_X1 --pins
liberty-parser cells.lib --attr voltage_unit
liberty-parser --version
Or without installing:
python -m liberty_parser cells.lib --cell INV_X1 --pins
pip install -e ".[dev]"
pytest -v- sdf-parser — Standard Delay Format
- verilog-parser — Verilog cell-library interfaces