Skip to content

Commit

Permalink
Merge pull request #18 from martinjankoehler/cli-usage
Browse files Browse the repository at this point in the history
CLI fixes and improvements
  • Loading branch information
martinjankoehler authored Dec 11, 2024
2 parents 89ab2df + 5008ad5 commit 3ea6e21
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 40 deletions.
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

0 comments on commit 3ea6e21

Please sign in to comment.