Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI fixes and improvements #18

Merged
merged 8 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Project Artifacts
testdata/designs/**/*.cir
output_*
output*

# Mac
.DS_Store
Expand Down
7 changes: 4 additions & 3 deletions klayout_pex/fastcap/fastcap_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ def run_fastcap(exe_path: str,
f"-l{lst_file_path}",
]

info(f"Calling {' '.join(args)}, output file: {log_path}")
info(f"Calling FastCap2")
subproc(f"{' '.join(args)}, output file: {log_path}")

rule()
rule('FastCap Output')
start = time.time()

proc = subprocess.Popen(args,
Expand All @@ -98,7 +99,7 @@ def run_fastcap(exe_path: str,
if proc.returncode == 0:
info(f"FastCap2 succeeded after {'%.4g' % duration}s")
else:
raise Exception(f"FastCap2 failed with status code {proc.returncode} after {'%.4g' % duration}s",
raise Exception(f"FastCap2 failed with status code {proc.returncode} after {'%.4g' % duration}s, "
f"see log file: {log_path}")


Expand Down
4 changes: 2 additions & 2 deletions klayout_pex/fastercap/fastercap_input_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

import klayout.db as kdb

from ..klayout.lvsdb_extractor import KLayoutExtractionContext, KLayoutExtractedLayerInfo, GDSPair
from ..klayout.lvsdb_extractor import KLayoutExtractionContext, GDSPair
from .fastercap_model_generator import FasterCapModelBuilder, FasterCapModelGenerator
from ..log import (
console,
Expand All @@ -46,7 +46,7 @@
)
from ..tech_info import TechInfo

import klayout_pex_protobuf.process_stack_pb2
import klayout_pex_protobuf.process_stack_pb2 as process_stack_pb2


class FasterCapInputBuilder:
Expand Down
9 changes: 5 additions & 4 deletions klayout_pex/fastercap/fastercap_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@
debug,
info,
warning,
error
error,
subproc
)


Expand Down Expand Up @@ -810,7 +811,7 @@ def write_fastcap(self, output_dir_path: str, prefix: str) -> str:
collation_operator = '' if idx == last_cond_index else ' +'
lst_file.append(f"C {fn} {'%.12g' % k_outside} 0 0 0{collation_operator}")

info(f"Writing FasterCap list file: {lst_fn}")
subproc(lst_fn)
with open(lst_fn, "w") as f:
f.write('\n'.join(lst_file))
f.write('\n')
Expand All @@ -823,7 +824,7 @@ def _write_fastercap_geo(output_path: str,
cond_number: int,
cond_name: Optional[str],
rename_conductor: bool):
info(f"Writing FasterCap geo file: {output_path}")
subproc(output_path)
with open(output_path, "w") as f:
f.write(f"0 GEO File\n")
for t in data:
Expand Down Expand Up @@ -973,7 +974,7 @@ def _write_as_stl(file_name: str,
if len(tris) == 0:
return

info(f"Writing STL file {file_name}")
subproc(file_name)
with open(file_name, "w") as f:
f.write("solid stl\n")
for t in tris:
Expand Down
7 changes: 4 additions & 3 deletions klayout_pex/fastercap/fastercap_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ def run_fastercap(exe_path: str,
args += [
lst_file_path
]
info(f"Calling {' '.join(args)}, output file: {log_path}")
info(f"Calling FasterCap")
subproc(f"{' '.join(args)}, output file: {log_path}")

rule()
rule('FasterCap Output')
start = time.time()

proc = subprocess.Popen(args,
Expand All @@ -97,7 +98,7 @@ def run_fastercap(exe_path: str,
if proc.returncode == 0:
info(f"FasterCap succeeded after {'%.4g' % duration}s")
else:
raise Exception(f"FasterCap failed with status code {proc.returncode} after {'%.4g' % duration}s",
raise Exception(f"FasterCap failed with status code {proc.returncode} after {'%.4g' % duration}s, "
f"see log file: {log_path}")


Expand Down
7 changes: 1 addition & 6 deletions klayout_pex/klayout/lvsdb_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import klayout.db as kdb

import klayout_pex_protobuf.tech_pb2
import klayout_pex_protobuf.tech_pb2 as tech_pb2
from ..log import (
console,
debug,
Expand Down Expand Up @@ -119,11 +119,6 @@ def prepare_extraction(cls,
tech=tech,
blackbox_devices=blackbox_devices)

rule('Non-empty layers in LVS database:')
for gds_pair, layer_info in extracted_layers.items():
names = [l.lvs_layer_name for l in layer_info.source_layers]
info(f"{gds_pair} -> ({' '.join(names)})")

return KLayoutExtractionContext(
lvsdb=lvsdb,
dbu=dbu,
Expand Down
93 changes: 78 additions & 15 deletions klayout_pex/kpex_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
import argparse
from datetime import datetime
from enum import StrEnum
from functools import cached_property
import logging
import os
import os.path

import rich.console
import rich.markdown
import rich.text
Expand Down Expand Up @@ -65,6 +67,7 @@
rule
)
from .magic.magic_runner import MagicPEXMode, run_magic, prepare_magic_script
from .pdk_config import PDKConfig
from .rcx25.extractor import RCExtractor, ExtractionResults
from .tech_info import TechInfo
from .util.multiple_choice import MultipleChoicePattern
Expand All @@ -77,11 +80,44 @@
PROGRAM_NAME = "kpex"


class ArgumentValidationError(Exception):
pass


class InputMode(StrEnum):
LVSDB = "lvsdb"
GDS = "gds"


# TODO: this should be externally configurable
class PDK(StrEnum):
IHP_SG13G2 = 'ihp_sg13g2'
SKY130A = 'sky130A'

@cached_property
def config(self) -> PDKConfig:
# NOTE: installation paths of resources in the distribution wheel differes from source repo
base_dir = os.path.dirname(os.path.realpath(__file__))
tech_pb_json_dir = base_dir
if os.path.isdir(os.path.join(base_dir, '..', '.git')): # in source repo
base_dir = os.path.dirname(base_dir)
tech_pb_json_dir = os.path.join(base_dir, 'build')

match self:
case PDK.IHP_SG13G2:
return PDKConfig(
name=self,
pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sg130g2.lvs'),
tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
)
case PDK.SKY130A:
return PDKConfig(
name=self,
pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sky130.lvs'),
tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
)


class KpexCLI:
@staticmethod
def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
Expand Down Expand Up @@ -114,10 +150,10 @@ def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
help="Path to klayout executable (default is '%(default)s')")

group_pex = main_parser.add_argument_group("Parasitic Extraction Setup")
group_pex.add_argument("--tech", "-t", dest="tech_pbjson_path", required=True,
help="Technology Protocol Buffer path (*.pb.json)")
group_pex.add_argument("--pdk", dest="pdk", required=True, type=PDK,
help=render_enum_help(topic='pdk', enum_cls=PDK))

group_pex.add_argument("--out_dir", "-o", dest="output_dir_base_path", default=".",
group_pex.add_argument("--out_dir", "-o", dest="output_dir_base_path", default="output",
help="Output directory path (default is '%(default)s')")

group_pex_input = main_parser.add_argument_group("Parasitic Extraction Input",
Expand All @@ -128,12 +164,7 @@ def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
group_pex_input.add_argument("--lvsdb", "-l", dest="lvsdb_path", help="KLayout LVSDB path (bypass LVS)")
group_pex_input.add_argument("--cell", "-c", dest="cell_name", default=None,
help="Cell (default is the top cell)")
default_lvs_script_path = os.path.realpath(os.path.join(__file__, '..', '..', 'pdk',
'sky130A', 'libs.tech', 'kpex', 'sky130.lvs'))

group_pex_input.add_argument("--lvs_script", dest="lvs_script_path",
default=default_lvs_script_path,
help=f"Path to KLayout LVS script (default is %(default)s)")
group_pex_input.add_argument("--cache-lvs", dest="cache_lvs",
type=true_or_false, default=True,
help="Used cached LVSDB (for given input GDS) (default is %(default)s)")
Expand Down Expand Up @@ -231,6 +262,10 @@ def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
def validate_args(args: argparse.Namespace):
found_errors = False

pdk_config: PDKConfig = args.pdk.config
args.tech_pbjson_path = pdk_config.tech_pb_json_path
args.lvs_script_path = pdk_config.pex_lvs_script_path

if not os.path.isfile(args.klayout_exe_path):
path = shutil.which(args.klayout_exe_path)
if not path:
Expand All @@ -241,6 +276,8 @@ def validate_args(args: argparse.Namespace):
error(f"Can't read technology file at path {args.tech_pbjson_path}")
found_errors = True

rule('Input Layout')

# input mode: LVS or existing LVSDB?
if args.gds_path:
info(f"GDS input file passed, running in LVS mode")
Expand Down Expand Up @@ -350,8 +387,23 @@ def input_file_stem(path: str):
error("Failed to parse --diel arg", e)
found_errors = True

# at least one engine must be activated

print("m#äh")
if not (args.run_magic or args.run_fastcap or args.run_fastercap or args.run_2_5D):
error("No PEX engines activated")
engine_help = """
| Argument | Description |
| -------------- | --------------------------------------- |
| --fastercap y | Run kpex/FasterCap engine |
| --2.5D y | Run kpex/2.5D engine |
| --magic y | Run MAGIC engine |
"""
subproc(f"\nPlease activate one or more engines using the arguments:\n{engine_help}")
found_errors = True

if found_errors:
raise Exception("Argument validation failed")
raise ArgumentValidationError("Argument validation failed")

def build_fastercap_input(self,
args: argparse.Namespace,
Expand All @@ -365,17 +417,19 @@ def build_fastercap_input(self,
delaunay_b=args.delaunay_b)
gen: FasterCapModelGenerator = fastercap_input_builder.build()

rule()
rule('FasterCap Input File Generation')
faster_cap_input_dir_path = os.path.join(args.output_dir_path, 'FasterCap_Input_Files')
os.makedirs(faster_cap_input_dir_path, exist_ok=True)

lst_file = gen.write_fastcap(output_dir_path=faster_cap_input_dir_path, prefix='FasterCap_Input_')

rule('STL File Generation')
geometry_dir_path = os.path.join(args.output_dir_path, 'Geometries')
os.makedirs(geometry_dir_path, exist_ok=True)
gen.dump_stl(output_dir_path=geometry_dir_path, prefix='')

if args.geometry_check:
rule('Geometry Validation')
gen.check()

return lst_file
Expand All @@ -385,6 +439,7 @@ def run_fastercap_extraction(self,
args: argparse.Namespace,
pex_context: KLayoutExtractionContext,
lst_file: str):
rule('FasterCap Execution')
info(f"Configure number of OpenMP threads (environmental variable OMP_NUM_THREADS) as {args.num_threads}")
os.environ['OMP_NUM_THREADS'] = f"{args.num_threads}"

Expand Down Expand Up @@ -489,6 +544,7 @@ def run_fastcap_extraction(self,
args: argparse.Namespace,
pex_context: KLayoutExtractionContext,
lst_file: str):
rule('FastCap2 Execution')
exe_path = "fastcap"
log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Output.txt")
raw_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Result_Matrix_Raw.csv")
Expand Down Expand Up @@ -603,7 +659,7 @@ def reregister_log_file_handler(handler: logging.Handler,
file_handler_formatted = register_log_file_handler(cli_log_path_formatted, formatter)
try:
self.validate_args(args)
except Exception:
except ArgumentValidationError:
if hasattr(args, 'output_dir_path'):
reregister_log_file_handler(file_handler_plain, cli_log_path_plain, None)
reregister_log_file_handler(file_handler_formatted, cli_log_path_formatted, formatter)
Expand Down Expand Up @@ -632,7 +688,8 @@ def create_lvsdb(self, args: argparse.Namespace) -> kdb.LayoutVsSchematic:

if os.path.exists(lvsdb_path) and args.cache_lvs:
if self.modification_date(lvsdb_path) > self.modification_date(args.gds_path):
warning(f"Reusing cached LVSDB at {lvsdb_path}")
warning(f"Reusing cached LVSDB")
subproc(lvsdb_path)
lvs_needed = False

if lvs_needed:
Expand All @@ -651,8 +708,8 @@ def main(self, argv: List[str]):
'--version' not in argv and \
'-h' not in argv and \
'--help' not in argv:
info("Called with arguments:")
info(' '.join(map(shlex.quote, sys.argv)))
rule('Command line arguments')
subproc(' '.join(map(shlex.quote, sys.argv)))

args = self.parse_args(argv[1:])

Expand All @@ -663,19 +720,25 @@ def main(self, argv: List[str]):
dielectric_filter=args.dielectric_filter)

if args.run_magic:
rule("MAGIC")
rule('MAGIC')
self.run_magic_extraction(args)

# no need to run LVS etc if only running magic engine
if not (args.run_fastcap or args.run_fastercap or args.run_2_5D):
return

rule('Prepare LVSDB')
lvsdb = self.create_lvsdb(args)

pex_context = KLayoutExtractionContext.prepare_extraction(top_cell=args.effective_cell_name,
lvsdb=lvsdb,
tech=tech_info,
blackbox_devices=args.blackbox_devices)
rule('Non-empty layers in LVS database')
for gds_pair, layer_info in pex_context.extracted_layers.items():
names = [l.lvs_layer_name for l in layer_info.source_layers]
info(f"{gds_pair} -> ({' '.join(names)})")

gds_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_l2n_extracted.gds.gz")
pex_context.target_layout.write(gds_path)

Expand Down
7 changes: 5 additions & 2 deletions klayout_pex/magic/magic_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ def run_magic(exe_path: str,
script_path, # TCL script
]

info(f"Calling {' '.join(args)}, output file: {log_path}")
info('Calling MAGIC')
subproc(f"{' '.join(args)}, output file: {log_path}")

rule('MAGIC Output')

start = time.time()

Expand All @@ -146,6 +149,6 @@ def run_magic(exe_path: str,
if proc.returncode == 0:
info(f"MAGIC succeeded after {'%.4g' % duration}s")
else:
raise Exception(f"MAGIC failed with status code {proc.returncode} after {'%.4g' % duration}s",
raise Exception(f"MAGIC failed with status code {proc.returncode} after {'%.4g' % duration}s, "
f"see log file: {log_path}")

Loading
Loading