# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Dict, Any, List, Tuple, Optional, Iterable, Mapping, Sequence
import asyncio
from pathlib import Path
from itertools import chain
from pybag.enum import LogLevel
from bag.io.file import read_yaml
from bag.simulation.base import get_corner_temp
from bag.core import BagProject
from bag3_liberty.enum import LogicType, TermType, LUTType
from bag3_liberty.data import Library, Cell, parse_cdba_name, get_bus_bit_name
from .char import LibertyCharMM
[docs]def generate_liberty(prj: BagProject, lib_config: Mapping[str, Any],
sim_config: Mapping[str, Any], specs: Mapping[str, Any],
fake: bool = False, extract: bool = False,
force_sim: bool = False, force_extract: bool = False,
gen_all_env: bool = False, gen_cell: bool = False, gen_cell_dut: bool = False,
gen_cell_tb: bool = False, export_lay: bool = False,
log_level: LogLevel = LogLevel.DEBUG) -> None:
gen_cell_dut |= gen_cell
gen_cell_tb |= gen_cell
asyncio.run(async_generate_liberty(prj, lib_config, sim_config, specs, fake=fake,
extract=extract, force_sim=force_sim,
force_extract=force_extract, gen_cell_dut=gen_cell_dut, gen_cell_tb=gen_cell_tb,
gen_all_env=gen_all_env, export_lay=export_lay,
log_level=log_level))
[docs]async def async_generate_liberty(prj: BagProject, lib_config: Mapping[str, Any],
sim_config: Mapping[str, Any], cell_specs: Mapping[str, Any],
fake: bool = False, extract: bool = False,
force_sim: bool = False, force_extract: bool = False,
gen_all_env: bool = False, gen_cell_dut: bool = False, gen_cell_tb: bool = False,
export_lay: bool = False, log_level: LogLevel = LogLevel.DEBUG
) -> None:
"""Generate liberty file for the given cells.
Parameters
----------
prj: BagProject
BagProject object to be able to generate things
lib_config : Mapping[str, Any]
library configuration dictionary.
sim_config : Mapping[str, Any]
simulation configuration dictionary.
cell_specs : Mapping[str, Any]
cell specification dictionary.
fake : bool
True to generate fake liberty file.
extract : bool
True to run extraction.
force_sim : bool
True to force simulation runs.
force_extract : bool
True to force extraction runs.
gen_all_env : bool
True to generate liberty files for all environments.
gen_cell_dut : bool
True to generate DUT schematics.
gen_cell_tb : bool
True to generate TB schematics.
export_lay : bool
Use CAD tool to export layout.
log_level : LogLevel
stdout logging level.
"""
gen_specs_file: str = cell_specs['gen_specs_file']
scenario: str = cell_specs.get('scenario', '')
gen_specs: Mapping[str, Any] = read_yaml(gen_specs_file)
impl_lib: str = gen_specs['impl_lib']
impl_cell: str = gen_specs['impl_cell']
dut_cls: str = gen_specs.get('dut_class', gen_specs.get('lay_class', ''))
dut_params: Optional[Mapping[str, Any]] = gen_specs.get('params', None)
name_prefix: str = gen_specs.get('name_prefix', '')
name_suffix: str = gen_specs.get('name_suffix', '')
gen_root_dir: Path = Path(gen_specs['root_dir'])
lib_root_dir = gen_root_dir / 'lib_gen'
sim_precision: int = sim_config['precision']
dsn_options = dict(
extract=extract,
force_extract=force_extract,
gen_sch_dut=gen_cell_dut,
gen_sch_tb=gen_cell_tb,
log_level=log_level,
)
log_file = str(lib_root_dir / 'lib_gen.log')
sim_db = prj.make_sim_db(lib_root_dir / 'dsn', log_file, impl_lib, dsn_options=dsn_options,
force_sim=force_sim, precision=sim_precision, log_level=log_level)
if dut_cls and dut_params is not None:
dut = await sim_db.async_new_design(impl_cell, dut_cls, dut_params, export_lay=export_lay,
name_prefix=name_prefix, name_suffix=name_suffix)
else:
dut = None
environments: Mapping[str, Any] = lib_config['environments']
nom_voltage_type: str = environments['nom_voltage_type']
name_format: str = environments['name_format']
voltage_precision: int = environments['voltage_precision']
sim_env_list: List[Mapping[str, Any]] = environments['sim_envs']
if not gen_all_env:
sim_env_list = [sim_env_list[0]]
voltage_fmt = '{:.%df}' % voltage_precision
lib_file_base_name = f'{impl_cell}_{scenario}' if scenario else impl_cell
for sim_env_config in sim_env_list:
sim_env: str = sim_env_config['sim_env']
voltages: Mapping[str, float] = sim_env_config['voltages']
vstr_table = {k: voltage_fmt.format(v).replace('.', 'p') for k, v in voltages.items()}
sim_env_name = name_format.format(sim_env=sim_env, **vstr_table)
lib_file_name = f'{lib_file_base_name}_{sim_env_name}'
cur_lib_config = dict(**lib_config)
cur_lib_config.pop('environments')
# setup lib config
temperature = get_corner_temp(sim_env)[1]
env_config = dict(
name=sim_env_name,
bag_name=sim_env,
process=1.0,
temperature=temperature,
voltage=voltages[nom_voltage_type],
)
cur_lib_config['voltages'] = voltages
cur_lib_config['sim_envs'] = [env_config]
lib = Library(f'{impl_cell}_{sim_env_name}', cur_lib_config)
out_file = gen_root_dir / f'{lib_file_name}.lib'
lib_data, mm_specs, cur_work_dir = get_cell_info(lib, impl_cell, cell_specs,
lib_root_dir, voltage_fmt)
mm_specs['fake'] = fake
mm_specs['sim_env_name'] = sim_env_name
for key in ['tran_tbm_specs', 'buf_params', 'in_cap_search_params', 'out_cap_search_params',
'seq_search_params', 'seq_delay_thres']:
mm_specs[key] = sim_config[key]
mm = sim_db.make_mm(LibertyCharMM, mm_specs)
sim_db.log(f'Characterizing {lib_file_name}.lib')
char_results = await sim_db.async_simulate_mm_obj('lib_char', cur_work_dir, dut, mm)
pin_data = char_results.data
_add_cell(lib, lib_data, pin_data)
lib.generate(out_file)
[docs]def get_cell_info(lib: Library, impl_cell: str, cell_specs: Mapping[str, Any], lib_root_dir: Path,
voltage_fmt: str) -> Tuple[Mapping[str, Any], Dict[str, Any], Path]:
sim_envs = lib.sim_envs
if len(sim_envs) != 1:
raise ValueError('Only support one corner per liberty file.')
in_cap_range_scale: float = cell_specs['input_cap_range_scale']
in_cap_min: float = cell_specs.get('input_cap_min', 1.0e-15)
in_cap_guess: float = cell_specs.get('input_cap_guess', in_cap_min)
out_min_fanout: float = cell_specs.get('min_fanout', 0.5)
stdcell_pwr_pins: Sequence[str] = cell_specs.get('stdcell_pwr_pins', [])
input_pins: Sequence[Mapping[str, Any]] = cell_specs.get('input_pins', [])
output_pins: Sequence[Mapping[str, Any]] = cell_specs.get('output_pins', [])
inout_pins: Sequence[Mapping[str, Any]] = cell_specs.get('inout_pins', [])
pwr_pins: Mapping[str, str] = cell_specs['pwr_pins']
gnd_pins: Mapping[str, str] = cell_specs['gnd_pins']
in_defaults: Mapping[str, Any] = cell_specs.get('input_pins_defaults', {})
out_defaults: Mapping[str, Any] = cell_specs.get('output_pins_defaults', {})
inout_defaults: Mapping[str, Any] = cell_specs.get('inout_pins_defaults', {})
pin_values: Mapping[str, int] = cell_specs.get('cond_defaults', {})
seq_timing: Mapping[str, Any] = cell_specs.get('seq_timing', {})
cell_props: Mapping[str, Any] = cell_specs.get('props', {})
diff_list: Sequence[Tuple[Sequence[str], Sequence[str]]] = cell_props.get('pin_opposite', [])
custom_meas: Mapping[str, Mapping[str, Any]] = cell_specs.get('custom_measurements', {})
# get supply values
sup_values: Dict[str, float] = {}
for pin, vtype in chain(pwr_pins.items(), gnd_pins.items()):
sup_values[pin] = lib.get_voltage(vtype)
# gather pin information
pwr_domain: Dict[str, Tuple[str, str]] = {}
reset_table: Dict[str, bool] = {}
in_cap_table: Dict[str, float] = {}
in_pin_list = _get_pin_info_list(input_pins, in_defaults, pwr_domain, reset_table,
in_cap_table, in_cap_guess)
io_pin_list = _get_pin_info_list(inout_pins, inout_defaults, pwr_domain)
out_pin_list = _get_pin_info_list(output_pins, out_defaults, pwr_domain)
lut_delay = lib.get_lut(LUTType.DELAY)
delay_shape = lut_delay.shape
delay_swp_info = lut_delay.get_swp_info(dict(trf_src='t_rf', cload='c_load'))
seq_swp_info = lut_delay.get_swp_info(dict(trf_src='t_clk_rf', cload='c_load'))
lut_cons = lib.get_lut(LUTType.CONSTRAINT)
t_rf_list = lut_cons['trf_in']
t_clk_rf_list = lut_cons['trf_src']
cons_swp_order = lut_cons.get_swp_order(dict(trf_src='t_clk_rf', trf_in='t_rf'))
out_io_info_table = dict(chain(_pin_info_iter(out_pin_list), _pin_info_iter(io_pin_list)))
out_cap_num_freq = len(lib.get_lut(LUTType.MAX_CAP)['freq'])
mm_specs = dict(
sim_envs=sim_envs,
thres_lo=lib.thres_lo,
thres_hi=lib.thres_hi,
in_cap_min_default=in_cap_min,
in_cap_range_scale=in_cap_range_scale,
out_max_trf=lib.get_max_input_transition(LogicType.COMB, is_clock=False),
out_min_fanout=out_min_fanout,
out_cap_num_freq=out_cap_num_freq,
in_cap_table=in_cap_table,
out_io_info_table=out_io_info_table,
seq_timing=seq_timing,
custom_meas=custom_meas,
in_pin_list=in_pin_list,
out_pin_list=out_pin_list,
io_pin_list=io_pin_list,
delay_shape=delay_shape,
delay_swp_info=delay_swp_info,
seq_shape=delay_shape,
seq_swp_info=seq_swp_info,
t_rf_list=t_rf_list,
t_clk_rf_list=t_clk_rf_list,
t_clk_rf_first=(cons_swp_order[0] == 't_clk_rf'),
dut_info=dict(
pwr_domain=pwr_domain,
sup_values=sup_values,
pin_values=pin_values,
reset_list=list(reset_table.items()),
diff_list=diff_list,
),
)
# get working directory
vstr_table = {k: voltage_fmt.format(v).replace('.', 'p') for k, v in sup_values.items()}
voltage_string = '_'.join((f'{k}_{vstr_table[k]}' for k in sorted(vstr_table.keys())))
work_dir = lib_root_dir / sim_envs[0] / voltage_string
# construct result dictionary
lib_data = {k: cell_specs[k] for k in ['props', 'pwr_pins', 'gnd_pins']}
lib_data['name'] = impl_cell
lib_data['stdcell_pwr_pins'] = stdcell_pwr_pins
lib_data['input_pins'] = in_pin_list
lib_data['output_pins'] = out_pin_list
lib_data['inout_pins'] = io_pin_list
return lib_data, mm_specs, work_dir
[docs]def _pin_info_iter(pin_list: List[Dict[str, Any]]) -> Iterable[Tuple[str, Dict[str, Any]]]:
for pin_info in pin_list:
basename: str = pin_info.get('basename', '')
if basename:
# bus pin
bus_range = pin_info['bus_range']
values: List[Dict[str, Any]] = pin_info['values']
for bus_idx, cur_info in zip(bus_range, values):
pin_name = get_bus_bit_name(basename, bus_idx, cdba=True)
yield pin_name, cur_info
else:
# scalar pin
yield pin_info['name'], pin_info
[docs]def _get_pin_info_list(src_list: Sequence[Mapping[str, Any]], defaults: Mapping[str, Any],
pwr_domain: Dict[str, Tuple[str, str]],
reset_table: Optional[Dict[str, bool]] = None,
in_cap_table: Optional[Dict[str, float]] = None, cap_guess: float = 1.0e-15
) -> List[Dict[str, Any]]:
pin_list = []
empty_dict = {}
default_dict = {k: v for k, v in defaults.items()}
for pin_info in src_list:
pin_name: str = pin_info['name']
reset_val: Optional[int] = pin_info.get('reset_val', None)
basename, bus_range = parse_cdba_name(pin_name)
if bus_range is None:
# scalar pin
if 'basename' in pin_info:
raise ValueError('Scalar pins cannot have basename entry defined.')
cur_info = default_dict.copy()
cur_info.update(pin_info)
cur_info.pop('hide', None)
in_cap_guess = cur_info.pop('cap_guess', cap_guess)
if in_cap_table is not None:
in_cap_table[pin_name] = in_cap_guess
# record power domain
pwr_domain[pin_name] = (cur_info['gnd_pin'], cur_info['pwr_pin'])
if reset_val is not None:
if reset_table is None:
raise ValueError('reset_table is not given but reset_val is defined.')
if pin_name in reset_table:
raise ValueError(f'pin {pin_name} is already in reset_table.')
reset_table[pin_name] = (reset_val == 1)
if not pin_info.get('hide', False):
pin_list.append(cur_info)
else:
# bus pin
values: Optional[List[Dict[str, Any]]] = pin_info.get('values', None)
bus_defaults: Dict[str, Any] = pin_info.get('defaults', empty_dict)
cur_defaults = default_dict.copy()
cur_defaults.update(bus_defaults)
if 'reset_val' not in cur_defaults:
cur_defaults['reset_val'] = None
num_bits = len(bus_range)
# NOTE: make dictionary copies, so we can add cap info to them later
if values is None:
values = [cur_defaults.copy() for _ in range(num_bits)]
elif len(values) != num_bits:
raise ValueError(f'values list of bus {pin_name} length mismatch')
else:
values = []
for val_ in values:
val_ = val_.copy()
val_.update(cur_defaults)
values.append(val_)
# record power domain and reset values
for bus_idx, bit_info in zip(bus_range, values):
pwr_str: str = _get('pwr_pin', bit_info, cur_defaults)
gnd_str: str = _get('gnd_pin', bit_info, cur_defaults)
reset_val: Optional[int] = _get('reset_val', bit_info, cur_defaults)
in_cap_guess = bit_info.pop('cap_guess', cap_guess)
bit_name = get_bus_bit_name(basename, bus_idx, cdba=True)
pwr_domain[bit_name] = (gnd_str, pwr_str)
if reset_val is not None:
if reset_table is None:
raise ValueError('reset_table is not given but reset_val is defined.')
if bit_name in reset_table:
raise ValueError(f'pin {bit_name} is already in reset_table.')
reset_table[bit_name] = (reset_val == 1)
if in_cap_table is not None:
in_cap_table[bit_name] = in_cap_guess
if not pin_info.get('hide', False):
pin_list.append(dict(name=pin_name, basename=basename, bus_range=bus_range,
values=values))
return pin_list
[docs]def _get(key: str, dict1: Mapping[str, Any], dict2: Mapping[str, Any]) -> Any:
ans = dict1.get(key, None)
if ans is not None:
return ans
return dict2[key]
[docs]def _add_cell(lib: Library, cell_info: Mapping[str, Any], pin_data: Mapping[str, Mapping[str, Any]]
) -> None:
empty_list = []
name: str = cell_info['name']
props: Dict[str, Any] = cell_info['props']
pwr_pins: Dict[str, str] = cell_info['pwr_pins']
gnd_pins: Dict[str, str] = cell_info['gnd_pins']
stdcell_pwr_pins: List[str] = cell_info.get('stdcell_pwr_pins', [])
input_pins: List[Dict[str, Any]] = cell_info.get('input_pins', empty_list)
output_pins: List[Dict[str, Any]] = cell_info.get('output_pins', empty_list)
inout_pins: List[Dict[str, Any]] = cell_info.get('inout_pins', empty_list)
cell = lib.create_cell(name, pwr_pins, gnd_pins, props, stdcell_pwr_pins=stdcell_pwr_pins)
_add_pins(cell, TermType.input, input_pins, pin_data)
_add_pins(cell, TermType.output, output_pins, pin_data)
_add_pins(cell, TermType.inout, inout_pins, pin_data)
[docs]def _add_pins(cell: Cell, pin_type: TermType, pin_list: List[Dict[str, Any]],
pin_data: Mapping[str, Mapping[str, Any]]) -> None:
remove_keys = ['reset_val', 'cap_info', 'timing_info']
for pin_info in pin_list:
basename: str = pin_info.get('basename', '')
if basename:
# bus pin
bus_range = pin_info['bus_range']
values: List[Dict[str, Any]] = pin_info['values']
bus = cell.create_bus(pin_info['name'], pin_type)
for idx, (bus_idx, cur_info) in enumerate(zip(bus_range, values)):
bit_name = get_bus_bit_name(basename, bus_idx, cdba=True)
for rm_key in remove_keys:
cur_info.pop(rm_key, None)
cur_info.update(pin_data[bit_name])
timing_list: Optional[List[Dict[str, Any]]] = cur_info.pop('timing', None)
pin = bus.create_pin(idx, pin_type, cur_info)
if timing_list is not None:
for timing in timing_list:
pin.add_timing(**timing)
else:
# scalar pin
pin_name = pin_info['name']
for rm_key in remove_keys:
pin_info.pop(rm_key, None)
pin_info.update(pin_data[pin_name])
timing_list: Optional[List[Dict[str, Any]]] = pin_info.pop('timing', None)
pin = cell.create_pin(pin_type, pin_info)
if timing_list is not None:
for timing in timing_list:
pin.add_timing(**timing)