From 030b6aeb80991a03b91f573e8745fe9834fddadb Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Thu, 21 May 2026 18:46:43 +0800 Subject: [PATCH 01/14] change config to workspace from step sub-workspace --- chipcompiler/data/__init__.py | 10 +- chipcompiler/data/workspace.py | 187 +++++++++++++++- chipcompiler/tools/ecc/builder.py | 219 ++----------------- chipcompiler/tools/ecc/module.py | 2 +- chipcompiler/tools/ecc/runner.py | 38 ++-- chipcompiler/tools/ecc/service.py | 4 +- chipcompiler/tools/ecc_dreamplace/builder.py | 20 +- chipcompiler/tools/ecc_dreamplace/module.py | 2 +- chipcompiler/tools/ecc_dreamplace/service.py | 4 +- chipcompiler/tools/eda.py | 2 +- chipcompiler/tools/yosys/builder.py | 5 - chipcompiler/tools/yosys/service.py | 10 +- 12 files changed, 248 insertions(+), 255 deletions(-) diff --git a/chipcompiler/data/__init__.py b/chipcompiler/data/__init__.py index 4efe2ea9..6fe9f113 100644 --- a/chipcompiler/data/__init__.py +++ b/chipcompiler/data/__init__.py @@ -11,13 +11,16 @@ OriginDesign, Workspace, WorkspaceStep, + build_workspace_config_paths, create_default_sdc, create_workspace, + init_workspace_config, load_workspace, log_workspace, log_parameters, log_flow, - log_workspace_step + log_workspace_step, + update_step_config, ) from .checklist import ( @@ -33,13 +36,16 @@ 'create_default_sdc', 'Workspace', 'WorkspaceStep', + 'build_workspace_config_paths', + 'init_workspace_config', + 'update_step_config', 'PDK', 'get_pdk', 'OriginDesign', 'log_workspace', 'log_parameters', 'log_flow', - 'log_workspace_step' + 'log_workspace_step', 'Parameters', 'load_parameter', 'save_parameter', diff --git a/chipcompiler/data/workspace.py b/chipcompiler/data/workspace.py index f0f26d60..aab9d329 100644 --- a/chipcompiler/data/workspace.py +++ b/chipcompiler/data/workspace.py @@ -13,6 +13,7 @@ from .home import HomeData from .pdk import get_pdk, PDK +from .step import StepEnum from chipcompiler.utility import Logger, create_logger, dict_to_str, find_files from chipcompiler.utility.filelist import parse_filelist, resolve_path, parse_incdir_directives @@ -46,6 +47,7 @@ class Workspace: parameters : Parameters = field(default_factory=Parameters) # design parameters flow : Flow = field(default_factory=Flow) # design flow for this workspace home : HomeData = field(default_factory=HomeData) # home data for this workspace + config : dict = field(default_factory=dict) # workspace-level config paths # logger logger : Logger = field(default_factory=Logger) # logger for this workspace @@ -64,7 +66,6 @@ class WorkspaceStep: version : str = "" # eda tool version # Paths for this step - config : dict = field(default_factory=dict) # config path about this step input : dict = field(default_factory=dict) # input path about this step output : dict = field(default_factory=dict) # output path about this step data : dict = field(default_factory=dict) # data path about this step @@ -86,7 +87,6 @@ def log_workspace_step(step : WorkspaceStep, logger : Logger): logger.info(f"step eda version : {step.version}") logger.info(f"step subworkspace : {step.directory}") - logger.info("\nconfig - \n%s", dict_to_str(step.config)) logger.info("\ninput - \n%s", dict_to_str(step.input)) logger.info("\noutput - \n%s", dict_to_str(step.output)) logger.info("\ndata - \n%s", dict_to_str(step.data)) @@ -98,6 +98,182 @@ def log_workspace_step(step : WorkspaceStep, logger : Logger): logger.info("\nsubflow - \n%s", dict_to_str(step.subflow)) logger.info("\nchecklist - \n%s", dict_to_str(step.checklist)) logger.log_separator() + + +def build_workspace_config_paths(workspace: Workspace) -> dict: + """Build workspace-level config file paths.""" + config_dir = f"{workspace.directory}/config" + return { + "dir": config_dir, + "flow": f"{config_dir}/flow_config.json", + "db": f"{config_dir}/db_default_config.json", + f"{StepEnum.CTS.value}": f"{config_dir}/cts_default_config.json", + f"{StepEnum.DRC.value}": f"{config_dir}/drc_default_config.json", + f"{StepEnum.FLOORPLAN.value}": f"{config_dir}/fp_default_config.json", + f"{StepEnum.NETLIST_OPT.value}": f"{config_dir}/no_default_config_fixfanout.json", + f"{StepEnum.PLACEMENT.value}": f"{config_dir}/pl_default_config.json", + f"{StepEnum.PNP.value}": f"{config_dir}/pnp_default_config.json", + f"{StepEnum.ROUTING.value}": f"{config_dir}/rt_default_config.json", + f"{StepEnum.TIMING_OPT_DRV.value}": f"{config_dir}/to_default_config_drv.json", + f"{StepEnum.TIMING_OPT_HOLD.value}": f"{config_dir}/to_default_config_hold.json", + f"{StepEnum.TIMING_OPT_SETUP.value}": f"{config_dir}/to_default_config_setup.json", + f"{StepEnum.LEGALIZATION.value}": f"{config_dir}/pl_default_config.json", + f"{StepEnum.FILLER.value}": f"{config_dir}/pl_default_config.json", + f"{StepEnum.RCX.value}": f"{config_dir}/rcx.json", + "dreamplace": f"{config_dir}/dreamplace.json", + } + + +def _ensure_writable(path: str): + import os + import stat + + for root, dirs, files in os.walk(path): + for name in dirs + files: + target = os.path.join(root, name) + try: + os.chmod(target, os.stat(target).st_mode | stat.S_IWUSR) + except OSError: + pass + + +def _copy_missing_files(src_dir: str, dst_dir: str): + import os + import shutil + + os.makedirs(dst_dir, exist_ok=True) + for name in os.listdir(src_dir): + src = os.path.join(src_dir, name) + dst = os.path.join(dst_dir, name) + if os.path.isfile(src) and not os.path.exists(dst): + shutil.copy2(src, dst) + + +def init_workspace_config(workspace: Workspace) -> None: + """Create workspace-level configs and write static fields once.""" + import os + import shutil + from copy import deepcopy + from chipcompiler.utility import json_read, json_write + + if not workspace.config: + workspace.config = build_workspace_config_paths(workspace) + + config_dir = workspace.config["dir"] + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = current_dir.rsplit("/", 1)[0] + ecc_config_dir = os.path.join(root_dir, "tools", "ecc", "configs") + dreamplace_config = os.path.join( + root_dir, + "tools", + "ecc_dreamplace", + "configs", + "dreamplace.json", + ) + + _copy_missing_files(ecc_config_dir, config_dir) + if not os.path.exists(workspace.config["dreamplace"]): + shutil.copy2(dreamplace_config, workspace.config["dreamplace"]) + _ensure_writable(config_dir) + + flow = json_read(workspace.config["flow"]) + flow["ConfigPath"]["idb_path"] = workspace.config["db"] + flow["ConfigPath"]["ifp_path"] = workspace.config[f"{StepEnum.FLOORPLAN.value}"] + flow["ConfigPath"]["ipl_path"] = workspace.config[f"{StepEnum.PLACEMENT.value}"] + flow["ConfigPath"]["irt_path"] = workspace.config[f"{StepEnum.ROUTING.value}"] + flow["ConfigPath"]["idrc_path"] = workspace.config[f"{StepEnum.DRC.value}"] + flow["ConfigPath"]["icts_path"] = workspace.config[f"{StepEnum.CTS.value}"] + flow["ConfigPath"]["ito_path"] = workspace.config[f"{StepEnum.TIMING_OPT_DRV.value}"] + flow["ConfigPath"]["ipnp_path"] = workspace.config[f"{StepEnum.PNP.value}"] + json_write(workspace.config["flow"], flow) + + db = json_read(workspace.config["db"]) + db["INPUT"]["tech_lef_path"] = workspace.pdk.tech + db["INPUT"]["lef_paths"] = workspace.pdk.lefs + db["INPUT"]["lib_path"] = workspace.pdk.libs + db["INPUT"]["sdc_path"] = workspace.pdk.sdc + db["INPUT"]["spef"] = workspace.pdk.spef + db["LayerSettings"]["routing_layer_1st"] = workspace.parameters.data.get("Bottom layer", "") + json_write(workspace.config["db"], db) + + fixfanout = json_read(workspace.config[f"{StepEnum.NETLIST_OPT.value}"]) + fixfanout["insert_buffer"] = workspace.pdk.buffers[0] if len(workspace.pdk.buffers) > 0 else "" + fixfanout["max_fanout"] = workspace.parameters.data.get("Max fanout", 32) + json_write(workspace.config[f"{StepEnum.NETLIST_OPT.value}"], fixfanout) + + placement = json_read(workspace.config[f"{StepEnum.PLACEMENT.value}"]) + placement["PL"]["BUFFER"]["buffer_type"] = workspace.pdk.buffers + placement["PL"]["Filler"]["first_iter"] = workspace.pdk.fillers + placement["PL"]["Filler"]["second_iter"] = workspace.pdk.fillers + placement["PL"]["GP"]["global_right_padding"] = workspace.parameters.data.get( + "Global right padding", 0 + ) + json_write(workspace.config[f"{StepEnum.PLACEMENT.value}"], placement) + + cts = json_read(workspace.config[f"{StepEnum.CTS.value}"]) + cts["buffer_type"] = workspace.pdk.buffers + json_write(workspace.config[f"{StepEnum.CTS.value}"], cts) + + drv = json_read(workspace.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) + drv["DRV_insert_buffers"] = workspace.pdk.buffers + json_write(workspace.config[f"{StepEnum.TIMING_OPT_DRV.value}"], drv) + + hold = json_read(workspace.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) + hold["hold_insert_buffers"] = workspace.pdk.buffers + json_write(workspace.config[f"{StepEnum.TIMING_OPT_HOLD.value}"], hold) + + setup = json_read(workspace.config[f"{StepEnum.TIMING_OPT_SETUP.value}"]) + setup["setup_insert_buffers"] = workspace.pdk.buffers + json_write(workspace.config[f"{StepEnum.TIMING_OPT_SETUP.value}"], setup) + + router = json_read(workspace.config[f"{StepEnum.ROUTING.value}"]) + router["RT"]["-bottom_routing_layer"] = workspace.parameters.data.get("Bottom layer", "") + router["RT"]["-top_routing_layer"] = workspace.parameters.data.get("Top layer", "") + json_write(workspace.config[f"{StepEnum.ROUTING.value}"], router) + + rcx = json_read(workspace.config[f"{StepEnum.RCX.value}"]) + rcx["mapping_file"] = workspace.pdk.mapping_file + corners = deepcopy(workspace.pdk.corners) + rcx["corners"] = corners + json_write(workspace.config[f"{StepEnum.RCX.value}"], rcx) + + dreamplace = json_read(workspace.config["dreamplace"]) + dreamplace["lef_input"] = [workspace.pdk.tech, *workspace.pdk.lefs] + dreamplace["base_design_name"] = workspace.design.name + dreamplace_overrides = workspace.parameters.data.get("DreamPlace", {}) + if isinstance(dreamplace_overrides, dict): + for key, value in dreamplace_overrides.items(): + dreamplace[key] = deepcopy(value) + json_write(workspace.config["dreamplace"], dreamplace) + + +def update_step_config(workspace: Workspace, step: WorkspaceStep) -> None: + """Update only step-dependent workspace config fields.""" + from chipcompiler.utility import json_read, json_write + + if not workspace.config: + workspace.config = build_workspace_config_paths(workspace) + + db = json_read(workspace.config["db"]) + db["INPUT"]["def_path"] = step.input.get("def", "") + db["INPUT"]["verilog_path"] = step.input.get("verilog", "") + db["OUTPUT"]["output_dir_path"] = step.output.get("dir", "") + json_write(workspace.config["db"], db) + + if step.name == StepEnum.ROUTING.value: + router = json_read(workspace.config[f"{StepEnum.ROUTING.value}"]) + router["RT"]["-temp_directory_path"] = step.data.get(f"{StepEnum.ROUTING.value}", "") + json_write(workspace.config[f"{StepEnum.ROUTING.value}"], router) + + if step.name == StepEnum.RCX.value: + rcx = json_read(workspace.config[f"{StepEnum.RCX.value}"]) + rcx_output_dir = step.output.get("dir", "") + rcx["output"] = rcx_output_dir + for corner in rcx.get("corners", []): + corner_name = corner.get("name", "") + if corner_name: + corner["spef_file"] = f"{rcx_output_dir}/{workspace.design.name}_{corner_name}.spef" + json_write(workspace.config[f"{StepEnum.RCX.value}"], rcx) def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=None) -> str: @@ -302,6 +478,7 @@ def create_workspace(directory : str, # update path workspace.directory = directory + workspace.config = build_workspace_config_paths(workspace) # create logger first (needed for copy operations) os.makedirs(f"{directory}/log", exist_ok=True) @@ -311,6 +488,7 @@ def create_workspace(directory : str, # update orign files to workspace origin folder import shutil os.makedirs(f"{directory}/origin", exist_ok=True) + os.makedirs(workspace.config["dir"], exist_ok=True) if os.path.exists(origin_def): shutil.copy(origin_def, f"{directory}/origin/{os.path.basename(origin_def)}") workspace.design.origin_def = f"{directory}/origin/{os.path.basename(origin_def)}" @@ -352,6 +530,8 @@ def create_workspace(directory : str, shutil.copy(workspace.pdk.spef, f"{directory}/origin/{os.path.basename(workspace.pdk.spef)}") workspace.pdk.spef = f"{directory}/origin/{os.path.basename(workspace.pdk.spef)}" + init_workspace_config(workspace) + # set home data os.makedirs(f"{directory}/home", exist_ok=True) workspace.flow.path = f"{directory}/home/flow.json" @@ -380,6 +560,7 @@ def load_workspace(directory : str) -> Workspace: # create workspace instance workspace = Workspace() workspace.directory = directory + workspace.config = build_workspace_config_paths(workspace) parameters = load_parameter(f"{directory}/home/parameters.json") if len(parameters.data)<=0: @@ -423,6 +604,7 @@ def load_workspace(directory : str) -> Workspace: # set home data os.makedirs(f"{directory}/home", exist_ok=True) + os.makedirs(workspace.config["dir"], exist_ok=True) workspace.flow.path = f"{directory}/home/flow.json" workspace.home.init(path=f"{directory}/home/home.json") workspace.home.set_flow(workspace.flow.path) @@ -444,6 +626,7 @@ def format_string(text : str, len=20) -> str: workspace.logger.log_section("workspace info") workspace.logger.info("workspace : %s", workspace.directory) + workspace.logger.info("config : %s", workspace.config.get("dir", "")) workspace.logger.info("PDK : %s", workspace.pdk.name) workspace.logger.info("design : %s", workspace.design.name) workspace.logger.info("top module : %s", workspace.design.top_module) diff --git a/chipcompiler/tools/ecc/builder.py b/chipcompiler/tools/ecc/builder.py index 9a0a4ad1..116fa67e 100644 --- a/chipcompiler/tools/ecc/builder.py +++ b/chipcompiler/tools/ecc/builder.py @@ -1,8 +1,15 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- import os -import stat -from chipcompiler.data import WorkspaceStep, Workspace, Parameters, StepEnum, StateEnum +from chipcompiler.data import ( + WorkspaceStep, + Workspace, + Parameters, + StepEnum, + StateEnum, + build_workspace_config_paths, + update_step_config, +) def build_step(workspace: Workspace, step_name: str, @@ -25,26 +32,6 @@ def build_step(workspace: Workspace, # build step directory step.directory = f"{workspace.directory}/{step.name}_{step.tool}" - # build config paths - step.config = { - "dir": f"{step.directory}/config", - "flow": f"{step.directory}/config/flow_config.json", - "db": f"{step.directory}/config/db_default_config.json", - f"{StepEnum.CTS.value}": f"{step.directory}/config/cts_default_config.json", - f"{StepEnum.DRC.value}": f"{step.directory}/config/drc_default_config.json", - f"{StepEnum.FLOORPLAN.value}": f"{step.directory}/config/fp_default_config.json", - f"{StepEnum.NETLIST_OPT.value}": f"{step.directory}/config/no_default_config_fixfanout.json", - f"{StepEnum.PLACEMENT.value}": f"{step.directory}/config/pl_default_config.json", - f"{StepEnum.PNP.value}": f"{step.directory}/config/pnp_default_config.json", - f"{StepEnum.ROUTING.value}": f"{step.directory}/config/rt_default_config.json", - f"{StepEnum.TIMING_OPT_DRV.value}": f"{step.directory}/config/to_default_config_drv.json", - f"{StepEnum.TIMING_OPT_HOLD.value}": f"{step.directory}/config/to_default_config_hold.json", - f"{StepEnum.TIMING_OPT_SETUP.value}": f"{step.directory}/config/to_default_config_setup.json", - f"{StepEnum.LEGALIZATION.value}": f"{step.directory}/config/pl_default_config.json", - f"{StepEnum.FILLER.value}": f"{step.directory}/config/pl_default_config.json", - f"{StepEnum.RCX.value}": f"{step.directory}/config/rcx.json" - } - # build input paths step.input = { "def": input_def, @@ -177,7 +164,6 @@ def build_step_space(step: WorkspaceStep) -> None: import os os.makedirs(step.directory, exist_ok=True) - os.makedirs(step.config.get("dir", f"{step.directory}/config"), exist_ok=True) os.makedirs(step.output.get("dir", f"{step.directory}/output"), exist_ok=True) os.makedirs(step.data.get("dir", f"{step.directory}/data"), exist_ok=True) os.makedirs(step.feature.get("dir", f"{step.directory}/feature"), exist_ok=True) @@ -209,185 +195,22 @@ def build_step_config(workspace: Workspace, build_checklist(workspace=workspace, workspace_step=step) - - # update config by parameters - from chipcompiler.utility import json_read, json_write - def _ensure_writable(path: str): - """Make files writable after copying from read-only sources.""" - for root, dirs, files in os.walk(path): - for name in dirs + files: - target = os.path.join(root, name) - try: - os.chmod(target, os.stat(target).st_mode | stat.S_IWUSR) - except OSError: - pass - - def _update_flow(): - # read config - config = json_read(step.config["flow"]) - - # parameters - config["ConfigPath"]["idb_path"] = step.config["db"] - config["ConfigPath"]["ifp_path"] = step.config[f"{StepEnum.FLOORPLAN.value}"] - config["ConfigPath"]["ipl_path"] = step.config[f"{StepEnum.PLACEMENT.value}"] - config["ConfigPath"]["irt_path"] = step.config[f"{StepEnum.ROUTING.value}"] - config["ConfigPath"]["idrc_path"] = step.config[f"{StepEnum.DRC.value}"] - config["ConfigPath"]["icts_path"] = step.config[f"{StepEnum.CTS.value}"] - config["ConfigPath"]["ito_path"] = step.config[f"{StepEnum.TIMING_OPT_DRV.value}"] - config["ConfigPath"]["ipnp_path"] = step.config[f"{StepEnum.PNP.value}"] - - # write back - json_write(step.config["flow"], config) - - def _update_db(): - # read config - config = json_read(step.config["db"]) - - # parameters - config["INPUT"]["tech_lef_path"] = workspace.pdk.tech - config["INPUT"]["lef_paths"] = workspace.pdk.lefs - config["INPUT"]["lib_path"] = workspace.pdk.libs - config["INPUT"]["sdc_path"] = workspace.pdk.sdc - config["INPUT"]["spef"] = workspace.pdk.spef - config["INPUT"]["def_path"] = step.input["def"] - config["INPUT"]["verilog_path"] = step.input["verilog"] - config["OUTPUT"]["output_dir_path"] = step.output["dir"] - config["LayerSettings"]["routing_layer_1st"] = workspace.parameters.data.get("Bottom layer", "") - - # write back - json_write(step.config["db"], config) - - def _update_fixfanout(): - # read config - config = json_read(step.config[f"{StepEnum.NETLIST_OPT.value}"]) - - # parameters - if len(workspace.pdk.buffers) > 0: - config["insert_buffer"] = workspace.pdk.buffers[0] - else: - config["insert_buffer"] = "" - - config["max_fanout"] = workspace.parameters.data.get("Max fanout", 32) - - # write back - json_write(step.config[f"{StepEnum.NETLIST_OPT.value}"], config) - - def _update_placement(): - # read config - config = json_read(step.config[f"{StepEnum.PLACEMENT.value}"]) - - # parameters - config["PL"]["BUFFER"]["buffer_type"] = workspace.pdk.buffers - config["PL"]["Filler"]["first_iter"] = workspace.pdk.fillers - config["PL"]["Filler"]["second_iter"] = workspace.pdk.fillers - - config["PL"]["GP"]["global_right_padding"] = workspace.parameters.data.get("Global right padding", 0) - - # write back - json_write(step.config[f"{StepEnum.PLACEMENT.value}"], config) - - def _update_cts(): - # read config - config = json_read(step.config[f"{StepEnum.CTS.value}"]) - - # parameters - config["buffer_type"] = workspace.pdk.buffers - - # write back - json_write(step.config[f"{StepEnum.CTS.value}"], config) - - def _update_drv(): - # read config - config = json_read(step.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) - - # parameters - config["DRV_insert_buffers"] = workspace.pdk.buffers - - # write back - json_write(step.config[f"{StepEnum.TIMING_OPT_DRV.value}"], config) - - def _update_hold(): - # read config - config = json_read(step.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) - - # parameters - config["hold_insert_buffers"] = workspace.pdk.buffers - - # write back - json_write(step.config[f"{StepEnum.TIMING_OPT_HOLD.value}"], config) - - def _update_setup(): - # read config - config = json_read(step.config[f"{StepEnum.TIMING_OPT_SETUP.value}"]) - - # parameters - config["setup_insert_buffers"] = workspace.pdk.buffers - - # write back - json_write(step.config[f"{StepEnum.TIMING_OPT_SETUP.value}"], config) - - def _update_router(): - # read config - config = json_read(step.config[f"{StepEnum.ROUTING.value}"]) - - # parameters - config["RT"]["-temp_directory_path"] = step.data.get(f"{StepEnum.ROUTING.value}", "") - config["RT"]["-bottom_routing_layer"] = workspace.parameters.data.get("Bottom layer", "") - config["RT"]["-top_routing_layer"] = workspace.parameters.data.get("Top layer", "") - - # write back - json_write(step.config[f"{StepEnum.ROUTING.value}"], config) - - def _update_rcx(): - if step.name != StepEnum.RCX.value: - return + if not workspace.config: + workspace.config = build_workspace_config_paths(workspace) - # read config - config = json_read(step.config[f"{StepEnum.RCX.value}"]) - - # parameters - config["output"] = step.output.get(f"dir", "") - config["mapping_file"] = workspace.pdk.mapping_file - - # update spef output path - output_spef_files = [] - for corner in workspace.pdk.corners: - corner_name = corner.get("name", "") - if corner_name != "": - output_spef = f"{step.output.get('dir', '')}/{workspace.design.name}_{corner_name}.spef" - corner["spef_file"] = output_spef - output_spef_files.append(output_spef) - - if corner_name == "TYPICAL": - workspace.pdk.spef = output_spef - - config["corners"] = workspace.pdk.corners - step.output["spef"] = output_spef_files - - # write back - json_write(step.config[f"{StepEnum.RCX.value}"], config) - - # copy files to origin folder - import shutil - current_dir = os.path.dirname(os.path.abspath(__file__)) - default_dir = os.path.abspath(os.path.join(current_dir, 'configs')) - shutil.copytree(default_dir, step.config["dir"], dirs_exist_ok=True) - _ensure_writable(step.config["dir"]) - # reload parameters from chipcompiler.data import load_parameter parameter = load_parameter(workspace.parameters.path) workspace.parameters = parameter - # update config by parameters - _update_flow() - _update_db() - _update_fixfanout() - _update_placement() - _update_cts() - _update_drv() - _update_hold() - _update_setup() - _update_router() - _update_rcx() + update_step_config(workspace=workspace, step=step) + + if step.name == StepEnum.RCX.value: + from chipcompiler.utility import json_read + rcx_config = json_read(workspace.config[f"{StepEnum.RCX.value}"]) + step.output["spef"] = [ + corner.get("spef_file", "") + for corner in rcx_config.get("corners", []) + if corner.get("spef_file", "") + ] diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index b17d7ef4..4e253543 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -17,7 +17,7 @@ class ECCToolsModule: """ def __init__(self): try: - from ecc_tools_bin import ecc_py as ecc + from chipcompiler.tools.ecc.bin import ecc_py as ecc except ImportError: try: from chipcompiler.tools.ecc.bin import ecc_py as ecc diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index 75d4cb65..b7647538 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -19,8 +19,8 @@ def create_db_engine(workspace: Workspace, def load_data(): eda_inst = ECCToolsModule() - eda_inst.init_config(flow_config=step.config["flow"], - db_config=step.config["db"], + eda_inst.init_config(flow_config=workspace.config["flow"], + db_config=workspace.config["db"], output_dir=step.data["dir"], feature_dir=step.feature["dir"]) @@ -35,8 +35,8 @@ def load_data(): def load_design(): eda_inst = ECCToolsModule() - eda_inst.init_config(flow_config=step.config["flow"], - db_config=step.config["db"], + eda_inst.init_config(flow_config=workspace.config["flow"], + db_config=workspace.config["db"], output_dir=step.data["dir"], feature_dir=step.feature["dir"]) @@ -250,7 +250,7 @@ def run_net_opt(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_net_opt(config=step.config[f"{StepEnum.NETLIST_OPT.value}"]) + eda_inst.run_net_opt(config=workspace.config[f"{StepEnum.NETLIST_OPT.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_net_optimization.value, state=StateEnum.Success) @@ -280,7 +280,7 @@ def run_placement(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_placement(config=step.config[f"{StepEnum.PLACEMENT.value}"]) + eda_inst.run_placement(config=workspace.config[f"{StepEnum.PLACEMENT.value}"]) eda_inst.feature_placement_map(json_path=step.feature["map"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_placement.value, state=StateEnum.Success) @@ -311,13 +311,13 @@ def run_cts(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_cts(config=step.config[f"{StepEnum.CTS.value}"], + eda_inst.run_cts(config=workspace.config[f"{StepEnum.CTS.value}"], output=step.data[f"{StepEnum.CTS.value}"]) eda_inst.report_cts(output=step.data[f"{StepEnum.CTS.value}"]) # Post-CTS legalization is handled by the following DreamPlace legalization step. - # eda_inst.run_legalize(config=step.config[f"{StepEnum.LEGALIZATION.value}"]) + # eda_inst.run_legalize(config=workspace.config[f"{StepEnum.LEGALIZATION.value}"]) eda_inst.feature_cts_map(json_path=step.feature["map"]) @@ -353,7 +353,7 @@ def run_timing_opt_drv(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_timing_opt_drv(config=step.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) + eda_inst.run_timing_opt_drv(config=workspace.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_timing_opt_drv.value, state=StateEnum.Success) @@ -383,7 +383,7 @@ def run_timing_opt_hold(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_timing_opt_hold(config=step.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) + eda_inst.run_timing_opt_hold(config=workspace.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_timing_opt_hold.value, state=StateEnum.Success) @@ -414,13 +414,13 @@ def run_routing(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - if eda_inst.is_rt_timing_enable(config=step.config[f"{StepEnum.ROUTING.value}"]): + if eda_inst.is_rt_timing_enable(config=workspace.config[f"{StepEnum.ROUTING.value}"]): eda_inst.init_sta(output_dir=step.data["sta"], top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, sdc_path=workspace.pdk.sdc) - eda_inst.run_routing(config=step.config[f"{StepEnum.ROUTING.value}"]) + eda_inst.run_routing(config=workspace.config[f"{StepEnum.ROUTING.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_routing.value, state=StateEnum.Success) @@ -454,7 +454,7 @@ def run_drc(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) eda_inst.init_drc(output_dir=step.data[f"{StepEnum.DRC.value}"]) - eda_inst.run_drc(config=step.config[f"{StepEnum.DRC.value}"], + eda_inst.run_drc(config=workspace.config[f"{StepEnum.DRC.value}"], report_path=step.report["step"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_DRC.value, state=StateEnum.Success) @@ -491,7 +491,7 @@ def run_legalization(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_legalize(config=step.config[f"{StepEnum.LEGALIZATION.value}"]) + eda_inst.run_legalize(config=workspace.config[f"{StepEnum.LEGALIZATION.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_legalization.value, state=StateEnum.Success) @@ -522,7 +522,7 @@ def run_filler(workspace: Workspace, if eda_inst is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_filler(config=step.config[f"{StepEnum.FILLER.value}"]) + eda_inst.run_filler(config=workspace.config[f"{StepEnum.FILLER.value}"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_filler.value, state=StateEnum.Success) @@ -580,7 +580,7 @@ def run_floorplan(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.init_floorplan.value, state=StateEnum.Success) - floorplan_dict = json_read(step.config.get(StepEnum.FLOORPLAN.value, "")) + floorplan_dict = json_read(workspace.config.get(StepEnum.FLOORPLAN.value, "")) # create tracks json_floorplan = floorplan_dict.get("Floorplan", {}) @@ -742,7 +742,7 @@ def run_rcx(workspace: Workspace, run rcx """ def run_jsons_to_itf(eda_inst : ECCToolsModule) -> bool: - config=json_read(step.config.get(StepEnum.RCX.value, "")) + config=json_read(workspace.config.get(StepEnum.RCX.value, "")) corners_dict = config.get("corners", []) for item in corners_dict: json_file = item.get("ecc_tf", "") @@ -769,7 +769,7 @@ def run_jsons_to_itf(eda_inst : ECCToolsModule) -> bool: sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Imcomplete) result = False else: - eda_inst.run_rcx(config=step.config.get(StepEnum.RCX.value, "")) + eda_inst.run_rcx(config=workspace.config.get(StepEnum.RCX.value, "")) eda_inst.report_rcx(step.output.get("dir", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Success) @@ -811,7 +811,7 @@ def collect_spef_files() -> list[tuple[str, str]]: for spef_file in spef_files ] - config = json_read(step.config.get(StepEnum.RCX.value, "")) + config = json_read(workspace.config.get(StepEnum.RCX.value, "")) spef_items = [] for corner in config.get("corners", []): spef_file = corner.get("spef_file", "") diff --git a/chipcompiler/tools/ecc/service.py b/chipcompiler/tools/ecc/service.py index 84271cf2..5c08090a 100644 --- a/chipcompiler/tools/ecc/service.py +++ b/chipcompiler/tools/ecc/service.py @@ -87,7 +87,7 @@ def build_subflow(workspace: Workspace, def build_config(workspace: Workspace, step: WorkspaceStep) -> dict: - cfg = step.config or {} + cfg = workspace.config or {} info = { "config": cfg.get(f"{step.name}", ""), } @@ -297,4 +297,4 @@ def build_sta(workspace: Workspace, "trans": sta_report.get("trans", os.path.join(sta_data_dir, f"{top_module}.trans")), } - return info \ No newline at end of file + return info diff --git a/chipcompiler/tools/ecc_dreamplace/builder.py b/chipcompiler/tools/ecc_dreamplace/builder.py index edc6a55b..e456aa5b 100644 --- a/chipcompiler/tools/ecc_dreamplace/builder.py +++ b/chipcompiler/tools/ecc_dreamplace/builder.py @@ -2,11 +2,10 @@ from __future__ import annotations -import shutil from copy import deepcopy -from pathlib import Path from chipcompiler.data import Workspace, WorkspaceStep +from chipcompiler.data import build_workspace_config_paths from chipcompiler.tools.ecc import builder as ecc_builder from chipcompiler.utility import json_read, json_write @@ -58,8 +57,6 @@ def build_step( tool="dreamplace", ) - step.config["dreamplace"] = f"{step.config['dir']}/dreamplace.json" - return step @@ -71,20 +68,13 @@ def build_step_config(workspace: Workspace, step: WorkspaceStep) -> None: # build ecc config ecc_builder.build_step_config(workspace, step) - # build workspace/place_dreamplace/config/dreamplace.json - - # resolve the absolute path to configs/dreamplace.json relative to this script, - # then copy it to the destination specified by step.config["dreamplace"] - param_src = Path(__file__).resolve().parent / "configs" / "dreamplace.json" - shutil.copy2(param_src, step.config["dreamplace"]) + if not workspace.config: + workspace.config = build_workspace_config_paths(workspace) - params = json_read(step.config["dreamplace"]) + params = json_read(workspace.config["dreamplace"]) - params["lef_input"] = [workspace.pdk.tech, *workspace.pdk.lefs] params["def_input"] = step.input.get("def", "") params["verilog_input"] = step.input.get("verilog", "") params["result_dir"] = step.data.get(step.name, step.data["dir"]) - params["base_design_name"] = workspace.design.name - params = apply_parameter_overrides(params, workspace.parameters.data) - json_write(step.config["dreamplace"], params) + json_write(workspace.config["dreamplace"], params) diff --git a/chipcompiler/tools/ecc_dreamplace/module.py b/chipcompiler/tools/ecc_dreamplace/module.py index cda4e8c8..f851d5e7 100644 --- a/chipcompiler/tools/ecc_dreamplace/module.py +++ b/chipcompiler/tools/ecc_dreamplace/module.py @@ -30,7 +30,7 @@ def __init__( self.input_verilog = input_verilog self.output_def = output_def self.output_verilog = output_verilog - self.param_path = step.config["dreamplace"] + self.param_path = workspace.config["dreamplace"] self.result_dir = step.data.get(step.name, step.data["dir"]) def _build_params(self, params_cls, legalize_only: bool): diff --git a/chipcompiler/tools/ecc_dreamplace/service.py b/chipcompiler/tools/ecc_dreamplace/service.py index fdef40db..690a3ecb 100644 --- a/chipcompiler/tools/ecc_dreamplace/service.py +++ b/chipcompiler/tools/ecc_dreamplace/service.py @@ -25,9 +25,9 @@ def get_step_info(workspace: Workspace, id=id) if id == "config": - step_info["config"] = step.config.get("dreamplace", "") + step_info["config"] = workspace.config.get("dreamplace", "") workspace.logger.log_section(f"[ecc dreamplace] get step info, id = {id}") workspace.logger.info(f"{dict_to_str(step_info)}") - return step_info \ No newline at end of file + return step_info diff --git a/chipcompiler/tools/eda.py b/chipcompiler/tools/eda.py index 2cc564b4..5721158e 100644 --- a/chipcompiler/tools/eda.py +++ b/chipcompiler/tools/eda.py @@ -80,7 +80,7 @@ def create_step(workspace : Workspace, eda_module.build_step_space(step) # update config - eda_module.build_step_config(workspace, step) + # eda_module.build_step_config(workspace, step) return step diff --git a/chipcompiler/tools/yosys/builder.py b/chipcompiler/tools/yosys/builder.py index 7b094024..5cdd6b1e 100644 --- a/chipcompiler/tools/yosys/builder.py +++ b/chipcompiler/tools/yosys/builder.py @@ -174,10 +174,6 @@ def build_step(workspace: Workspace, step.directory = f"{workspace.directory}/{step.name}_{step.tool}" - step.config = { - "dir": f"{step.directory}/config", - } - step.input = { "verilog": input_verilog, } @@ -248,7 +244,6 @@ def build_step_space(step: WorkspaceStep) -> None: Create the workspace directories for the given step. """ os.makedirs(step.directory, exist_ok=True) - os.makedirs(step.config.get("dir", f"{step.directory}/config"), exist_ok=True) os.makedirs(step.output.get("dir", f"{step.directory}/output"), exist_ok=True) os.makedirs(step.data.get("dir", f"{step.directory}/data"), exist_ok=True) os.makedirs(step.data.get("tmp", f"{step.directory}/data/tmp"), exist_ok=True) diff --git a/chipcompiler/tools/yosys/service.py b/chipcompiler/tools/yosys/service.py index 3b23b0df..8e90c28c 100644 --- a/chipcompiler/tools/yosys/service.py +++ b/chipcompiler/tools/yosys/service.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -import os -from chipcompiler.data import Workspace, WorkspaceStep, StepMetrics, save_metrics +from chipcompiler.data import Workspace, WorkspaceStep from chipcompiler.tools.yosys.metrics import build_step_metrics from chipcompiler.utility import dict_to_str @@ -72,10 +71,7 @@ def build_subflow(workspace: Workspace, def build_config(workspace: Workspace, step: WorkspaceStep) -> dict: - path = (step.config or {}).get("flow", "") - if not path and step.directory: - path = os.path.join(step.directory, "config", "flow_config.json") - return {"path": path} + return {"path": workspace.config.get("flow", "")} def build_analysis(workspace: Workspace, step: WorkspaceStep) -> dict: @@ -101,4 +97,4 @@ def build_checklist(workspace: Workspace, "path" : step.checklist.get("path", "") } - return info \ No newline at end of file + return info From c32cdbdcb9cce2f369b27f8371f01babafed445c Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 26 May 2026 11:08:47 +0800 Subject: [PATCH 02/14] change ecc flow from subprocess to single process and change config path --- chipcompiler/engine/db.py | 26 +- chipcompiler/engine/flow.py | 105 +++----- chipcompiler/thirdparty/ecc-tools | 2 +- chipcompiler/tools/ecc/module.py | 24 +- chipcompiler/tools/ecc/runner.py | 271 +++++++++++--------- chipcompiler/tools/ecc_dreamplace/module.py | 2 + chipcompiler/tools/ecc_dreamplace/runner.py | 18 +- test/test_tools.py | 7 +- 8 files changed, 229 insertions(+), 226 deletions(-) diff --git a/chipcompiler/engine/db.py b/chipcompiler/engine/db.py index 50f4ab10..26a76e96 100644 --- a/chipcompiler/engine/db.py +++ b/chipcompiler/engine/db.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep +from chipcompiler.data import Workspace, WorkspaceStep, StepEnum class EngineDB: """ @@ -8,21 +8,27 @@ class EngineDB: db : use ecc-tools-idb as the database engine """ from chipcompiler.tools.ecc import ECCToolsModule - def __init__(self, workspace : Workspace, eda : ECCToolsModule= None): + def __init__(self, workspace : Workspace, ecc_module : ECCToolsModule= None): self.workspace = workspace - self.eda = eda + self.ecc_module = ecc_module + + def has_init(self) -> bool: + return self.ecc_module is not None and self.ecc_module.ecc is not None @property def engine(self): - return self.eda + return self.ecc_module def create_db_engine(self, step: WorkspaceStep) -> bool: """ create db engine from ecc module """ - if self.eda is not None: + if self.ecc_module is not None: return True + if step is None: + return False + # check eda tool exist from chipcompiler.tools import load_eda_module eda_module = load_eda_module("ecc") @@ -30,12 +36,12 @@ def create_db_engine(self, step: WorkspaceStep) -> bool: return False from chipcompiler.tools.ecc import create_db_engine - self.eda = create_db_engine(self.workspace, step) - if self.eda is not None: - self.workspace.logger.info("ecc db initialize success.") + self.ecc_module = create_db_engine(self.workspace, step) + if self.ecc_module is not None: + self.workspace.logger.info(f"ecc db initialize success for step {step.name}.") return True else: - self.workspace.logger.error("ecc db initialize failed.") + self.workspace.logger.warning(f"ecc db initialize failed for step {step.name}.") return False def update_db_from_step(self, @@ -48,4 +54,4 @@ def update_db_from_step(self, """ def_file = step.output["def"] - self.eda.read_def() \ No newline at end of file + self.ecc_module.read_def() \ No newline at end of file diff --git a/chipcompiler/engine/flow.py b/chipcompiler/engine/flow.py index b885af57..ddd05e7a 100644 --- a/chipcompiler/engine/flow.py +++ b/chipcompiler/engine/flow.py @@ -15,54 +15,11 @@ logger = logging.getLogger(__name__) -def _run_step_in_subprocess(workspace: Workspace, workspace_step: WorkspaceStep) -> None: - """ - Step subprocess entry point: redirect stdio to log file if configured, - then execute the EDA tool step. - """ - # Redirect stdout/stderr to the step's own log file. - log_file = workspace_step.log.get("file", "") - if log_file: - log_file = os.path.abspath(log_file) - try: - os.makedirs(os.path.dirname(log_file) or ".", exist_ok=True) - redirect_stdio_to_file(log_file) - except Exception: - traceback.print_exc() - - step_tag = f"{workspace_step.name}({workspace_step.tool})" - workspace.logger.info(f"[STEP] {step_tag} pid={os.getpid()} started") - - try: - from chipcompiler.tools import run_step as run_tool_step - result = run_tool_step(workspace=workspace, step=workspace_step) - workspace.logger.info(f"[STEP] {step_tag} finished result={result}") - except Exception: - workspace.logger.error(f"[STEP] {step_tag} failed with exception") - traceback.print_exc() - - -def _run_step_inline(workspace: Workspace, workspace_step: WorkspaceStep) -> None: - """ - Execute a step in the current process so pdb/debugpy can attach normally. - """ - step_tag = f"{workspace_step.name}({workspace_step.tool})" - workspace.logger.info(f"[STEP] {step_tag} started inline pid={os.getpid()}") - - try: - from chipcompiler.tools import run_step as run_tool_step - result = run_tool_step(workspace=workspace, step=workspace_step) - workspace.logger.info(f"[STEP] {step_tag} finished inline result={result}") - except Exception: - workspace.logger.error(f"[STEP] {step_tag} failed with exception") - traceback.print_exc() - - class EngineFlow: - def __init__(self, workspace : Workspace): + def __init__(self, workspace : Workspace, engine_db : EngineDB = None): self.workspace = workspace self.workspace_steps = [] - self.db = None # db engine for this flow + self.engine_db = engine_db # db engine for this flow if self.workspace is not None: self.load() @@ -276,25 +233,24 @@ def init_db_engine(self) -> bool: if len(self.workspace_steps) <= 0: return False - if self.db is not None: - return True + # check ecc is initialized by last step, if exist and success, use it to init db engine directly. + if self.engine_db is None: + self.engine_db = EngineDB(workspace=self.workspace) + else: + if self.engine_db.has_init(): + return True # init engine step by last workpsace step data if all step run success - workspace_step = self.workspace_steps[-1] + workspace_step = None for ws_step in self.workspace_steps: if not self.check_state(name=ws_step.name, tool=ws_step.tool, state=StateEnum.Success): # use the first unsuccess step to setup db engine workspace_step = ws_step + break - engine = EngineDB(workspace=self.workspace) - if engine.create_db_engine(step=workspace_step): - self.db = engine - return True - else: - return False - + return self.engine_db.create_db_engine(step=workspace_step) def run_steps(self, rerun=False) -> bool: """ @@ -305,7 +261,7 @@ def run_steps(self, rerun=False) -> bool: for workspace_step in self.workspace_steps: self.workspace.logger.log_section(f"{workspace_step.tool} - begin step - {workspace_step.name}") - + self.init_db_engine() state = self.run_step(workspace_step, rerun) log_flow(workspace=self.workspace) @@ -352,24 +308,27 @@ def run_step(self, tool=workspace_step.tool, state=StateEnum.Ongoing) - # run step in a subprocess - p = Process(target=_run_step_in_subprocess, - args=(self.workspace, workspace_step)) - p.start() - step_log_file = workspace_step.log.get("file", "") - logger.info("[DISPATCH] %s pid=%s log=%s", step_tag, p.pid, - os.path.abspath(step_log_file) if step_log_file else "N/A") - - # track peak memory in a background thread - peak_memory_result = [0] - def _track_memory(): - peak_memory_result[0] = track_process_memory(p.pid) - tracker = Thread(target=_track_memory, daemon=True) - tracker.start() + # run step + log_file = workspace_step.log.get("file", "") + if log_file: + log_file = os.path.abspath(log_file) + try: + os.makedirs(os.path.dirname(log_file) or ".", exist_ok=True) + redirect_stdio_to_file(log_file) + except Exception: + traceback.print_exc() + + step_tag = f"{workspace_step.name}({workspace_step.tool})" + self.workspace.logger.info(f"[STEP] {step_tag} pid={os.getpid()} started") + + try: + from chipcompiler.tools import run_step as run_tool_step + result = run_tool_step(workspace=self.workspace, step=workspace_step, ecc_module=self.engine_db.engine) + self.workspace.logger.info(f"[STEP] {step_tag} finished result={result}") + except Exception: + self.workspace.logger.error(f"[STEP] {step_tag} failed with exception") + traceback.print_exc() - p.join() - tracker.join(timeout=1.0) - # compute metrics peak_memory_mb = 0 elapsed = time.time() - start_time diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index 0cf3b52b..50acc7f1 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit 0cf3b52bc218d664cac86730f508def85fb469ee +Subproject commit 50acc7f116e221e9991034ecb1ad6f3a8c87f2f2 diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index 4e253543..385ee324 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -17,7 +17,7 @@ class ECCToolsModule: """ def __init__(self): try: - from chipcompiler.tools.ecc.bin import ecc_py as ecc + from ecc_tools_bin import ecc_py as ecc except ImportError: try: from chipcompiler.tools.ecc.bin import ecc_py as ecc @@ -64,7 +64,10 @@ def build_macro_connection_map(self, max_hop: int): def build_connection_map(self, clusters, src_instances, max_hop: int): return self.ecc.build_connection_map(clusters, src_instances, max_hop) - + + def reset_data(self): + self.ecc.reset_data() + ######################################################################## # config api ######################################################################## @@ -83,6 +86,12 @@ def init_config(self, output_path=output_dir, feature_path=feature_dir, ) + + def update_step_paths(self, output_dir: str, feature_dir: str): + self.ecc.db_init( + output_path=output_dir, + feature_path=feature_dir, + ) ######################################################################## # data api @@ -869,11 +878,14 @@ def is_rt_timing_enable(self, config : str): ######################################################################## # RCX api ######################################################################## - def run_rcx(self, config: str): - return self.ecc.run_rcx(config=config) + def init_rcx(self, config: str): + return self.ecc.init_rcx(config=config) + + def run_rcx(self): + return self.ecc.run_rcx() - def report_rcx(self, output_dir: str = "."): - return self.ecc.report_rcx(output_dir) + def report_rcx(self): + return self.ecc.report_rcx() def rcx_json_to_itf(self, json_path: str, itf_path: str): rcx_extractor =self.RcxExtraction(json_path, itf_path) diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index b7647538..8a7790cb 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -16,56 +16,68 @@ def create_db_engine(workspace: Workspace, step: WorkspaceStep) -> ECCToolsModule: """""" - def load_data(): - eda_inst = ECCToolsModule() + def load_data(): + ecc_module = ECCToolsModule() - eda_inst.init_config(flow_config=workspace.config["flow"], - db_config=workspace.config["db"], - output_dir=step.data["dir"], - feature_dir=step.feature["dir"]) + ecc_module.init_config(flow_config=workspace.config.get("flow"), + db_config=workspace.config.get("db"), + output_dir=step.data.get("dir"), + feature_dir=step.feature.get("dir")) db_path = step.input.get("db", "") - if eda_inst.is_db_data_exists(db_path): - eda_inst.load_data(path=db_path) + if ecc_module.is_db_data_exists(db_path): + ecc_module.load_data(path=db_path) workspace.logger.info(f"Successfully loaded data from {db_path}") - return eda_inst + return ecc_module else: return None def load_design(): - eda_inst = ECCToolsModule() + ecc_module = ECCToolsModule() - eda_inst.init_config(flow_config=workspace.config["flow"], - db_config=workspace.config["db"], - output_dir=step.data["dir"], - feature_dir=step.feature["dir"]) + ecc_module.init_config(flow_config=workspace.config.get("flow"), + db_config=workspace.config.get("db"), + output_dir=step.data.get("dir"), + feature_dir=step.feature.get("dir")) - eda_inst.init_techlef(workspace.pdk.tech) - eda_inst.init_lefs(workspace.pdk.lefs) + ecc_module.init_techlef(workspace.pdk.tech) + ecc_module.init_lefs(workspace.pdk.lefs) # if db def exist, read db def - if os.path.exists(step.input["def"]): - eda_inst.read_def(step.input["def"]) + if os.path.exists(step.input.get("def", "")): + ecc_module.read_def(step.input.get("def", "")) else: #else, read step output verilog - if os.path.exists(step.input["verilog"]): - eda_inst.read_verilog(verilog=step.input["verilog"], + if os.path.exists(step.input.get("verilog", "")): + ecc_module.read_verilog(verilog=step.input.get("verilog", ""), top_module=workspace.design.top_module) else: return None - return eda_inst + return ecc_module - if not is_eda_exist(): + def is_enable_setup(): + # skip synthesis step + if step.name == StepEnum.SYNTHESIS.value: + return False + + # db_path = step.input.get("db", "") + + # ecc_module = ECCToolsModule() + + # return ecc_module.is_db_data_exists(db_path) or os.path.exists(step.input.get("def", "")) or os.path.exists(step.input.get("verilog", "")) + return os.path.exists(step.input.get("def", "")) or os.path.exists(step.input.get("verilog", "")) + + if not is_eda_exist() or not is_enable_setup(): return None try: - eda_inst = load_data() - if eda_inst is None: - eda_inst = load_design() + ecc_module = load_data() + if ecc_module is None: + ecc_module = load_design() except Exception as e: - eda_inst = load_design() + ecc_module = load_design() - return eda_inst + return ecc_module def get_eda_instance(workspace: Workspace, step: WorkspaceStep, @@ -74,17 +86,23 @@ def get_eda_instance(workspace: Workspace, ecc_module is ecc module from db engine, eda instacnce may initialize data from this module if ecc_module has been set """ - eda_inst = None + if ecc_module is None: + try: + ecc_module = create_db_engine(workspace=workspace, + step=step) + except Exception as e: + ecc_module = None + workspace.logger.error(f"Failed to create ECC engine for step {step.name}: {e}") + + # release sta for some memory leakage issue if ecc_module is not None: - # copy data from ecc_module, but not set ecc_module to eda inst - # TBD - eda_inst = ecc_module - else: - # init ecc module - eda_inst = create_db_engine(workspace=workspace, - step=step) + ecc_module.update_step_paths( + output_dir=step.data.get("dir", ""), + feature_dir=step.feature.get("dir", ""), + ) + ecc_module.release_sta() - return eda_inst + return ecc_module def save_data(workspace: Workspace, step: WorkspaceStep, @@ -100,7 +118,7 @@ def save_data(workspace: Workspace, ecc_module.def_save(def_path=step.output.get("def", "")) ecc_module.verilog_save(output_verilog=step.output.get("verilog", "")) ecc_module.gds_save(output_path=step.output.get("gds", "")) - # ecc_module.save_data(path=step.output.get("db", "")) + ecc_module.save_data(path=step.output.get("db", "")) ecc_module.json_save(path=step.output.get("json", "")) ecc_module.feature_sammry(json_path=step.feature.get("db", "")) if feature_step: @@ -110,11 +128,13 @@ def save_data(workspace: Workspace, ecc_module.report_summary(path=step.report.get("db", "")) # report timing + ecc_module.release_sta() ecc_module.init_sta(output_dir=step.data.get("sta", ""), top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, sdc_path=workspace.pdk.sdc) ecc_module.report_timing() + ecc_module.release_sta() # update parameters db_json = json_read(step.feature.get("db", "")) @@ -244,17 +264,17 @@ def run_net_opt(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_net_opt(config=workspace.config[f"{StepEnum.NETLIST_OPT.value}"]) + ecc_module.run_net_opt(config=workspace.config.get(f"{StepEnum.NETLIST_OPT.value}")) sub_flow.update_step(step_name=EccSubFlowEnum.run_net_optimization.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -273,19 +293,19 @@ def run_placement(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_placement(config=workspace.config[f"{StepEnum.PLACEMENT.value}"]) - eda_inst.feature_placement_map(json_path=step.feature["map"]) + ecc_module.run_placement(config=workspace.config.get(f"{StepEnum.PLACEMENT.value}")) + ecc_module.feature_placement_map(json_path=step.feature.get("map", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_placement.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -304,26 +324,26 @@ def run_cts(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_cts(config=workspace.config[f"{StepEnum.CTS.value}"], - output=step.data[f"{StepEnum.CTS.value}"]) + ecc_module.run_cts(config=workspace.config.get(f"{StepEnum.CTS.value}", ""), + output=step.data.get(f"{StepEnum.CTS.value}", "")) - eda_inst.report_cts(output=step.data[f"{StepEnum.CTS.value}"]) + ecc_module.report_cts(output=step.data.get(f"{StepEnum.CTS.value}", "")) # Post-CTS legalization is handled by the following DreamPlace legalization step. - # eda_inst.run_legalize(config=workspace.config[f"{StepEnum.LEGALIZATION.value}"]) + # ecc_module.run_legalize(config=workspace.config.get(f"{StepEnum.LEGALIZATION.value}", "")) - eda_inst.feature_cts_map(json_path=step.feature["map"]) + ecc_module.feature_cts_map(json_path=step.feature.get("map", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_CTS.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -342,22 +362,22 @@ def run_timing_opt_drv(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_timing_opt_drv(config=workspace.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) + ecc_module.run_timing_opt_drv(config=workspace.config.get(f"{StepEnum.TIMING_OPT_DRV.value}", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_timing_opt_drv.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -376,18 +396,18 @@ def run_timing_opt_hold(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_timing_opt_hold(config=workspace.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) + ecc_module.run_timing_opt_hold(config=workspace.config.get(f"{StepEnum.TIMING_OPT_HOLD.value}", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_timing_opt_hold.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -406,25 +426,25 @@ def run_routing(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - if eda_inst.is_rt_timing_enable(config=workspace.config[f"{StepEnum.ROUTING.value}"]): - eda_inst.init_sta(output_dir=step.data["sta"], + if ecc_module.is_rt_timing_enable(config=workspace.config.get(f"{StepEnum.ROUTING.value}", "")): + ecc_module.init_sta(output_dir=step.data.get(f"{StepEnum.ROUTING.value}", ""), top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, sdc_path=workspace.pdk.sdc) - eda_inst.run_routing(config=workspace.config[f"{StepEnum.ROUTING.value}"]) + ecc_module.run_routing(config=workspace.config.get(f"{StepEnum.ROUTING.value}", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_routing.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -445,23 +465,23 @@ def run_drc(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.init_drc(output_dir=step.data[f"{StepEnum.DRC.value}"]) - eda_inst.run_drc(config=workspace.config[f"{StepEnum.DRC.value}"], - report_path=step.report["step"]) + ecc_module.init_drc(output_dir=step.data.get(f"{StepEnum.DRC.value}", "")) + ecc_module.run_drc(config=workspace.config.get(f"{StepEnum.DRC.value}", ""), + report_path=step.report.get("step", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_DRC.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) - eda_inst.save_drc(feature_path=step.feature[f"step"]) + ecc_module.save_drc(feature_path=step.feature.get("step", "")) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -484,18 +504,18 @@ def run_legalization(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_legalize(config=workspace.config[f"{StepEnum.LEGALIZATION.value}"]) + ecc_module.run_legalize(config=workspace.config.get(f"{StepEnum.LEGALIZATION.value}", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_legalization.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -515,18 +535,18 @@ def run_filler(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.run_filler(config=workspace.config[f"{StepEnum.FILLER.value}"]) + ecc_module.run_filler(config=workspace.config.get(f"{StepEnum.FILLER.value}", "")) sub_flow.update_step(step_name=EccSubFlowEnum.run_filler.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -545,11 +565,11 @@ def run_floorplan(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) @@ -558,7 +578,7 @@ def run_floorplan(workspace: Workspace, util = workspace.parameters.data.get("Core", {}).get("Utilitization", 0.3) margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) aspect_ratio = workspace.parameters.data.get("Core", {}).get("Aspect ratio", 1) - eda_inst.init_floorplan_by_core_utilization( + ecc_module.init_floorplan_by_core_utilization( core_site=workspace.pdk.site_core, io_site=workspace.pdk.site_io, corner_site=workspace.pdk.site_corner, @@ -571,7 +591,7 @@ def run_floorplan(workspace: Workspace, # init by die and core area # die_area=workspace.parameters.data.get("Die", {}).get("Bounding box", "") # core_area=workspace.parameters.data.get("Core", {}).get("Bounding box", "") - # eda_inst.init_floorplan_by_area(die_area=die_area, + # ecc_module.init_floorplan_by_area(die_area=die_area, # core_area=core_area, # core_site=workspace.pdk.site_core, # io_site=workspace.pdk.site_io, @@ -586,7 +606,7 @@ def run_floorplan(workspace: Workspace, json_floorplan = floorplan_dict.get("Floorplan", {}) json_track = json_floorplan.get("Tracks", []) for item in json_track: - eda_inst.gern_track(layer=item.get("layer", ""), + ecc_module.gern_track(layer=item.get("layer", ""), x_start=item.get("x start", 0), x_step=item.get("x step", 0), y_start=item.get("y start", 0), @@ -598,7 +618,7 @@ def run_floorplan(workspace: Workspace, json_macro_placement = floorplan_dict.get("Macro Placement", []) if len(json_macro_placement) > 0: for item in json_macro_placement: - eda_inst.place_instance( + ecc_module.place_instance( inst_name=item.get("inst_name", ""), llx=item.get("llx", 0), lly=item.get("lly", 0), @@ -616,7 +636,7 @@ def run_floorplan(workspace: Workspace, net_name = item.get("net name", "") direction = item.get("direction", "") is_power = item.get("is power") - eda_inst.add_pdn_io(net_name=net_name, + ecc_module.add_pdn_io(net_name=net_name, direction=direction, is_power=is_power) @@ -626,13 +646,13 @@ def run_floorplan(workspace: Workspace, net_name = item.get("net name", "") instance_pin_name = item.get("instance pin name", "") is_power = item.get("is power", 1) - eda_inst.global_net_connect(net_name=net_name, + ecc_module.global_net_connect(net_name=net_name, instance_pin_name=instance_pin_name, is_power=is_power) # auto place io pins json_iopin_place = json_floorplan.get("Auto place pin", {}) - eda_inst.auto_place_pins(layer=json_iopin_place.get("layer", ""), + ecc_module.auto_place_pins(layer=json_iopin_place.get("layer", ""), width=json_iopin_place.get("width", 0), height=json_iopin_place.get("height", 0), sides=json_iopin_place.get("sides", [])) @@ -640,7 +660,7 @@ def run_floorplan(workspace: Workspace, state=StateEnum.Success) # tap cell - eda_inst.tapcell(tapcell=workspace.pdk.tap_cell, + ecc_module.tapcell(tapcell=workspace.pdk.tap_cell, distance=json_floorplan.get("Tap distance", 0), endcap=workspace.pdk.end_cap) sub_flow.update_step(step_name=EccSubFlowEnum.tap_cell.value, @@ -654,7 +674,7 @@ def run_floorplan(workspace: Workspace, ground_net = json_pdn_grid.get("ground net", "") width = json_pdn_grid.get("width", 0) offset = json_pdn_grid.get("offset", 0) - eda_inst.create_pdn_grid(layer=layer, + ecc_module.create_pdn_grid(layer=layer, net_power=power_net, net_ground=ground_net, width=width, @@ -669,7 +689,7 @@ def run_floorplan(workspace: Workspace, width = item.get("width", 0) pitch = item.get("pitch", 0) offset = item.get("offset", 0) - eda_inst.create_pdn_stripe(layer=layer, + ecc_module.create_pdn_stripe(layer=layer, net_power=power_net, net_ground=ground_net, width=width, @@ -681,19 +701,19 @@ def run_floorplan(workspace: Workspace, for item in json_pdn_connect_layers: layers = item.get("layers", []) if len(layers) >= 2: - eda_inst.connect_pdn_layers(layers) + ecc_module.connect_pdn_layers(layers) sub_flow.update_step(step_name=EccSubFlowEnum.PDN.value, state=StateEnum.Success) # set clock net clock_name = workspace.parameters.data.get("Clock", "") - eda_inst.set_net(net_name=clock_name, + ecc_module.set_net(net_name=clock_name, net_type="CLOCK") sub_flow.update_step(step_name=EccSubFlowEnum.set_clock_net.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=eda_inst, feature_step=False) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -713,21 +733,21 @@ def run_harden(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: - eda_inst.init_sta(output_dir=step.data["sta"], + if ecc_module is not None: + ecc_module.init_sta(output_dir=step.data["sta"], top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, sdc_path=workspace.pdk.sdc) - eda_inst.update_timing() + ecc_module.update_timing() sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - eda_inst.write_abstract_lef(output_lef_path=step.output.get("lef", "")) - eda_inst.write_timing_model(output_lib_path=step.output.get("lib", "")) - eda_inst.gds_save(output_path=step.output.get("gds", ""), is_harden=True) + ecc_module.write_abstract_lef(output_lef_path=step.output.get("lef", "")) + ecc_module.write_timing_model(output_lib_path=step.output.get("lib", "")) + ecc_module.gds_save(output_path=step.output.get("gds", ""), is_harden=True) sub_flow.update_step(step_name=EccSubFlowEnum.run_harden.value, state=StateEnum.Success) @@ -741,7 +761,7 @@ def run_rcx(workspace: Workspace, """ run rcx """ - def run_jsons_to_itf(eda_inst : ECCToolsModule) -> bool: + def run_jsons_to_itf(ecc_module : ECCToolsModule) -> bool: config=json_read(workspace.config.get(StepEnum.RCX.value, "")) corners_dict = config.get("corners", []) for item in corners_dict: @@ -751,7 +771,7 @@ def run_jsons_to_itf(eda_inst : ECCToolsModule) -> bool: if not os.path.exists(json_file): return False - eda_inst.rcx_json_to_itf(json_path=json_file, itf_path=itf_file) + ecc_module.rcx_json_to_itf(json_path=json_file, itf_path=itf_file) return True result = False @@ -759,21 +779,22 @@ def run_jsons_to_itf(eda_inst : ECCToolsModule) -> bool: sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - if not run_jsons_to_itf(eda_inst): + if not run_jsons_to_itf(ecc_module): sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Imcomplete) result = False else: - eda_inst.run_rcx(config=workspace.config.get(StepEnum.RCX.value, "")) - eda_inst.report_rcx(step.output.get("dir", "")) + ecc_module.init_rcx(config=workspace.config.get(StepEnum.RCX.value, "")) + ecc_module.run_rcx() + ecc_module.report_rcx() sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Success) - save_data(workspace=workspace, step=step, ecc_module=eda_inst, feature_step=False) + save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -828,11 +849,11 @@ def collect_spef_files() -> list[tuple[str, str]]: sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - eda_inst = get_eda_instance(workspace=workspace, + ecc_module = get_eda_instance(workspace=workspace, step=step, ecc_module = ecc_module) - if eda_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) spef_items = collect_spef_files() @@ -872,16 +893,16 @@ def collect_spef_files() -> list[tuple[str, str]]: os.makedirs(report_dir, exist_ok=True) try: - eda_inst.set_design_workspace(report_dir) - eda_inst.read_netlist(step.input.get("verilog", "")) - eda_inst.read_liberty(workspace.pdk.libs) - eda_inst.link_design(workspace.design.top_module) - eda_inst.read_sdc(workspace.pdk.sdc) - eda_inst.read_spef(file_name=spef_file) - eda_inst.report_timing() + ecc_module.set_design_workspace(report_dir) + ecc_module.read_netlist(step.input.get("verilog", "")) + ecc_module.read_liberty(workspace.pdk.libs) + ecc_module.link_design(workspace.design.top_module) + ecc_module.read_sdc(workspace.pdk.sdc) + ecc_module.read_spef(file_name=spef_file) + ecc_module.report_timing() finally: # release sta - eda_inst.release_sta() + ecc_module.release_sta() workspace.logger.info("STA report for %s saved to %s", spef_file, @@ -889,7 +910,7 @@ def collect_spef_files() -> list[tuple[str, str]]: sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Success) - result = save_data(workspace=workspace, step=step, ecc_module=eda_inst, feature_step=False) + result = save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) diff --git a/chipcompiler/tools/ecc_dreamplace/module.py b/chipcompiler/tools/ecc_dreamplace/module.py index f851d5e7..343ba5fb 100644 --- a/chipcompiler/tools/ecc_dreamplace/module.py +++ b/chipcompiler/tools/ecc_dreamplace/module.py @@ -47,6 +47,8 @@ def _build_params(self, params_cls, legalize_only: bool): params.timing_opt_flag = 0 params.timing_eval_flag = 0 params.differentiable_timing_obj = 0 + params.routability_opt_flag = 0 + params.get_congestion_map = 0 if legalize_only: params.global_place_flag = 0 diff --git a/chipcompiler/tools/ecc_dreamplace/runner.py b/chipcompiler/tools/ecc_dreamplace/runner.py index 1d0fe3d1..c9825c28 100644 --- a/chipcompiler/tools/ecc_dreamplace/runner.py +++ b/chipcompiler/tools/ecc_dreamplace/runner.py @@ -46,18 +46,18 @@ def run_placement(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - ecc_inst = ecc_runner.get_eda_instance(workspace=workspace, + ecc_module = ecc_runner.get_eda_instance(workspace=workspace, step=step, ecc_module=ecc_module) - if ecc_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) # run ecc dreamplace dreamplace_module = DreamplaceModule( workspace=workspace, step=step, - ecc_module=ecc_inst, + ecc_module=ecc_module, input_def=step.input.get("def", ""), input_verilog=step.input.get("verilog", ""), output_def=step.output.get("def", ""), @@ -65,11 +65,11 @@ def run_placement(workspace: Workspace, ) reslut = dreamplace_module.run_placement() - ecc_inst.feature_placement_map(json_path=step.feature["map"]) + ecc_module.feature_placement_map(json_path=step.feature["map"]) sub_flow.update_step(step_name=EccSubFlowEnum.run_placement.value, state=StateEnum.Success) - reslut = ecc_runner.save_data(workspace=workspace, step=step, ecc_module=ecc_inst, feature_step=False) + reslut = ecc_runner.save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -90,18 +90,18 @@ def run_legalization(workspace: Workspace, sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - ecc_inst = ecc_runner.get_eda_instance(workspace=workspace, + ecc_module = ecc_runner.get_eda_instance(workspace=workspace, step=step, ecc_module=ecc_module) - if ecc_inst is not None: + if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) # run ecc dreamplace dreamplace_module = DreamplaceModule( workspace=workspace, step=step, - ecc_module=ecc_inst, + ecc_module=ecc_module, input_def=step.input.get("def", ""), input_verilog=step.input.get("verilog", ""), output_def=step.output.get("def", ""), @@ -111,7 +111,7 @@ def run_legalization(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_legalization.value, state=StateEnum.Success) - reslut = ecc_runner.save_data(workspace=workspace, step=step, ecc_module=ecc_inst, feature_step=False) + reslut = ecc_runner.save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) diff --git a/test/test_tools.py b/test/test_tools.py index 58ec0a99..b740f969 100644 --- a/test/test_tools.py +++ b/test/test_tools.py @@ -41,8 +41,11 @@ def test_ics55_gcd(): ) # workspace = load_workspace(workspace_dir) - - engine_flow = EngineFlow(workspace=workspace) + from chipcompiler.engine import EngineDB + # build engine_db for workspace + engine_db = EngineDB(workspace=workspace) + # build engine flow for workspace + engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) if not engine_flow.has_init(): from chipcompiler.rtl2gds import build_rtl2gds_flow steps = build_rtl2gds_flow() From 43faaf19e5262219cb4dcd7db6a7eefa2836d01c Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Thu, 28 May 2026 09:36:49 +0800 Subject: [PATCH 03/14] update rcx config and ecc-tools --- chipcompiler/data/pdk.py | 47 ++++++++++++++----------- chipcompiler/rtl2gds/builder.py | 2 +- chipcompiler/thirdparty/ecc-tools | 2 +- chipcompiler/tools/ecc/configs/rcx.json | 5 +++ 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index f91734f0..09499b78 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -90,42 +90,47 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: "{}/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib".format(stdcell_dir), "{}/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ss_rcworst_1p08_125_nldm.lib".format(stdcell_dir) ] - mapping_file = "{}/resource/ICsprout_55LLULP_1P6M_5lc_V1p1_cell.map".format(resolved_root) + mapping_file = "{}/corners/ICsprout_55LLULP_1P6M_5lc_V1p1_cell.map".format(resolved_root) corners = [ { "name" : "TYPICAL", - "ecc_tf" : "{}/resource/TYP.json".format(resolved_root), - "itf_file" : "{}/resource/TYP.itf".format(resolved_root), - "captab_file" : "{}/resource/TYP.captab".format(resolved_root), - "spef_file" : "{}/resource/TYP.spef".format(resolved_root) + "temperature" : 25, + "ecc_tf" : "{}/corners/TYP.json".format(resolved_root), + "itf_file" : "{}/corners/TYP.itf".format(resolved_root), + "captab_file" : "{}/corners/TYP.captab".format(resolved_root), + "spef_file" : "{}/corners/TYP.spef".format(resolved_root) }, { "name" : "RCbest", - "ecc_tf" : "{}/resource/RCbest.json".format(resolved_root), - "itf_file" : "{}/resource/RCbest.itf".format(resolved_root), - "captab_file" : "{}/resource/RCbest.captab".format(resolved_root), - "spef_file" : "{}/resource/RCbest.spef".format(resolved_root) + "temperature" : 25, + "ecc_tf" : "{}/corners/RCbest.json".format(resolved_root), + "itf_file" : "{}/corners/RCbest.itf".format(resolved_root), + "captab_file" : "{}/corners/RCbest.captab".format(resolved_root), + "spef_file" : "{}/corners/RCbest.spef".format(resolved_root) }, { "name" : "RCworst", - "ecc_tf" : "{}/resource/RCworst.json".format(resolved_root), - "itf_file" : "{}/resource/RCworst.itf".format(resolved_root), - "captab_file" : "{}/resource/RCworst.captab".format(resolved_root), - "spef_file" : "{}/resource/RCworst.spef".format(resolved_root) + "temperature" : 25, + "ecc_tf" : "{}/corners/RCworst.json".format(resolved_root), + "itf_file" : "{}/corners/RCworst.itf".format(resolved_root), + "captab_file" : "{}/corners/RCworst.captab".format(resolved_root), + "spef_file" : "{}/corners/RCworst.spef".format(resolved_root) }, { "name" : "Cbest", - "ecc_tf" : "{}/resource/Cbest.json".format(resolved_root), - "itf_file" : "{}/resource/Cbest.itf".format(resolved_root), - "captab_file" : "{}/resource/Cbest.captab".format(resolved_root), - "spef_file" : "{}/resource/Cbest.spef".format(resolved_root) + "temperature" : 25, + "ecc_tf" : "{}/corners/Cbest.json".format(resolved_root), + "itf_file" : "{}/corners/Cbest.itf".format(resolved_root), + "captab_file" : "{}/corners/Cbest.captab".format(resolved_root), + "spef_file" : "{}/corners/Cbest.spef".format(resolved_root) }, { "name" : "Cworst", - "ecc_tf" : "{}/resource/Cworst.json".format(resolved_root), - "itf_file" : "{}/resource/Cworst.itf".format(resolved_root), - "captab_file" : "{}/resource/Cworst.captab".format(resolved_root), - "spef_file" : "{}/resource/Cworst.spef".format(resolved_root) + "temperature" : 25, + "ecc_tf" : "{}/corners/Cworst.json".format(resolved_root), + "itf_file" : "{}/corners/Cworst.itf".format(resolved_root), + "captab_file" : "{}/corners/Cworst.captab".format(resolved_root), + "spef_file" : "{}/corners/Cworst.spef".format(resolved_root) } ] diff --git a/chipcompiler/rtl2gds/builder.py b/chipcompiler/rtl2gds/builder.py index c5f1876c..5ce8f0a1 100644 --- a/chipcompiler/rtl2gds/builder.py +++ b/chipcompiler/rtl2gds/builder.py @@ -22,7 +22,7 @@ def build_rtl2gds_flow() -> list: return steps def build_harden_flow() -> list: - steps = build_rtl2gds_flow() + steps = build_rcx_flow() steps.append((StepEnum.HARDEN, "ecc", StateEnum.Unstart)) diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index 50acc7f1..10b82633 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit 50acc7f116e221e9991034ecb1ad6f3a8c87f2f2 +Subproject commit 10b8263366e104e557bb644c020dd4ff28433b06 diff --git a/chipcompiler/tools/ecc/configs/rcx.json b/chipcompiler/tools/ecc/configs/rcx.json index d676e874..5eb3ee37 100644 --- a/chipcompiler/tools/ecc/configs/rcx.json +++ b/chipcompiler/tools/ecc/configs/rcx.json @@ -5,30 +5,35 @@ "corners" : [ { "name" : "TYPICAL", + "temperature" : 25, "ecc_tf" : "./TYP.json", "itf_file" : "./TYP.itf ", "captab_file" : "./TYP.captab" }, { "name" : "RCbest", + "temperature" : 25, "ecc_tf" : "./RCbest.json", "itf_file" : "./RCbest.itf ", "captab_file" : "./RCbest.captab" }, { "name" : "RCworst", + "temperature" : 25, "ecc_tf" : "./RCworst.json", "itf_file" : "./RCworst.itf ", "captab_file" : "./RCworst.captab" }, { "name" : "Cbest", + "temperature" : 25, "ecc_tf" : "./Cbest.json", "itf_file" : "./Cbest.itf ", "captab_file" : "./Cbest.captab" }, { "name" : "Cworst", + "temperature" : 25, "ecc_tf" : "./Cworst.json", "itf_file" : "./Cworst.itf ", "captab_file" : "./Cworst.captab" From 3974eb4bad7957551f703ca5dd537a2395a89f2f Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Thu, 28 May 2026 09:40:50 +0800 Subject: [PATCH 04/14] update submodule --- .gitignore | 1 + chipcompiler/thirdparty/ecc-dreamplace | 2 +- chipcompiler/thirdparty/ecc-tools | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8a5f20e5..22b334b3 100644 --- a/.gitignore +++ b/.gitignore @@ -189,3 +189,4 @@ docs/superpowers/ findings.md progress.md task_plan.md +.agents/ diff --git a/chipcompiler/thirdparty/ecc-dreamplace b/chipcompiler/thirdparty/ecc-dreamplace index d30cc89f..23f48c06 160000 --- a/chipcompiler/thirdparty/ecc-dreamplace +++ b/chipcompiler/thirdparty/ecc-dreamplace @@ -1 +1 @@ -Subproject commit d30cc89fbf843849b813a3ca2d1935ff61f8a7ed +Subproject commit 23f48c063f8dc65c9b0b2e2627cee689ddbb2dc2 diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index 50acc7f1..10b82633 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit 50acc7f116e221e9991034ecb1ad6f3a8c87f2f2 +Subproject commit 10b8263366e104e557bb644c020dd4ff28433b06 From e2add8d417d66ee3fd7a7086d2bfcce4a24a9dd4 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Fri, 29 May 2026 16:20:23 +0800 Subject: [PATCH 05/14] change sta and rcx json file --- chipcompiler/data/pdk.py | 10 ++-- chipcompiler/thirdparty/ecc-tools | 2 +- chipcompiler/tools/ecc/configs/rcx.json | 75 ++++++++++++++++--------- chipcompiler/tools/ecc/configs/sta.json | 53 +++++++++++++++++ 4 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 chipcompiler/tools/ecc/configs/sta.json diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index 09499b78..2213e2b0 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -94,7 +94,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: corners = [ { "name" : "TYPICAL", - "temperature" : 25, + "temperature" : [25], "ecc_tf" : "{}/corners/TYP.json".format(resolved_root), "itf_file" : "{}/corners/TYP.itf".format(resolved_root), "captab_file" : "{}/corners/TYP.captab".format(resolved_root), @@ -102,7 +102,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: }, { "name" : "RCbest", - "temperature" : 25, + "temperature" : [-40, 125], "ecc_tf" : "{}/corners/RCbest.json".format(resolved_root), "itf_file" : "{}/corners/RCbest.itf".format(resolved_root), "captab_file" : "{}/corners/RCbest.captab".format(resolved_root), @@ -110,7 +110,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: }, { "name" : "RCworst", - "temperature" : 25, + "temperature" : [-40, 125], "ecc_tf" : "{}/corners/RCworst.json".format(resolved_root), "itf_file" : "{}/corners/RCworst.itf".format(resolved_root), "captab_file" : "{}/corners/RCworst.captab".format(resolved_root), @@ -118,7 +118,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: }, { "name" : "Cbest", - "temperature" : 25, + "temperature" : [-40, 125], "ecc_tf" : "{}/corners/Cbest.json".format(resolved_root), "itf_file" : "{}/corners/Cbest.itf".format(resolved_root), "captab_file" : "{}/corners/Cbest.captab".format(resolved_root), @@ -126,7 +126,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: }, { "name" : "Cworst", - "temperature" : 25, + "temperature" : [-40, 125], "ecc_tf" : "{}/corners/Cworst.json".format(resolved_root), "itf_file" : "{}/corners/Cworst.itf".format(resolved_root), "captab_file" : "{}/corners/Cworst.captab".format(resolved_root), diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index 10b82633..2f243466 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit 10b8263366e104e557bb644c020dd4ff28433b06 +Subproject commit 2f24346665fb9f76066fc953d0c4c75523e38bbe diff --git a/chipcompiler/tools/ecc/configs/rcx.json b/chipcompiler/tools/ecc/configs/rcx.json index 5eb3ee37..66e01a2c 100644 --- a/chipcompiler/tools/ecc/configs/rcx.json +++ b/chipcompiler/tools/ecc/configs/rcx.json @@ -1,42 +1,61 @@ { "thread_num": 64, - "output" : "", - "mapping_file" : "", - "corners" : [ + "output": "/RCX_ecc/output", + "mapping_file": "/corners/ICsprout_55LLULP_1P6M_5lc_V1p1_cell.map", + "corners": [ { - "name" : "TYPICAL", - "temperature" : 25, - "ecc_tf" : "./TYP.json", - "itf_file" : "./TYP.itf ", - "captab_file" : "./TYP.captab" + "name": "TYPICAL", + "temperature": [25], + "ecc_tf": "/corners/TYP.json", + "itf_file": "/corners/TYP.itf", + "captab_file": "/corners/TYP.captab", + "spef_file": [ + { "25" : "/RCX_ecc/output/gcd_TYPICAL_25C.spef" } + ] }, { - "name" : "RCbest", - "temperature" : 25, - "ecc_tf" : "./RCbest.json", - "itf_file" : "./RCbest.itf ", - "captab_file" : "./RCbest.captab" + "name": "RCbest", + "temperature": [-40, 125], + "ecc_tf": "/corners/RCbest.json", + "itf_file": "/corners/RCbest.itf", + "captab_file": "/corners/RCbest.captab", + "spef_file": [ + { "-40" : "/RCX_ecc/output/gcd_RCbest_m40C.spef" }, + { "125" : "/RCX_ecc/output/gcd_RCbest_125C.spef" } + ] }, { - "name" : "RCworst", - "temperature" : 25, - "ecc_tf" : "./RCworst.json", - "itf_file" : "./RCworst.itf ", - "captab_file" : "./RCworst.captab" + "name": "RCworst", + "temperature": [-40, 125], + "ecc_tf": "/corners/RCworst.json", + "itf_file": "/corners/RCworst.itf", + "captab_file": "/corners/RCworst.captab", + "spef_file": [ + { "-40" : "/RCX_ecc/output/gcd_RCworst_m40C.spef" }, + { "125" : "/RCX_ecc/output/gcd_RCworst_125C.spef" } + ] }, { - "name" : "Cbest", - "temperature" : 25, - "ecc_tf" : "./Cbest.json", - "itf_file" : "./Cbest.itf ", - "captab_file" : "./Cbest.captab" + "name": "Cbest", + "temperature": [-40, 125], + "ecc_tf": "/corners/Cbest.json", + "itf_file": "/corners/Cbest.itf", + "captab_file": "/corners/Cbest.captab", + "spef_file": [ + { "-40" : "/RCX_ecc/output/gcd_Cbest_m40C.spef" }, + { "125" : "/RCX_ecc/output/gcd_Cbest_125C.spef" } + ] }, { - "name" : "Cworst", - "temperature" : 25, - "ecc_tf" : "./Cworst.json", - "itf_file" : "./Cworst.itf ", - "captab_file" : "./Cworst.captab" + "name": "Cworst", + "temperature": [-40, 125], + "ecc_tf": "/corners/Cworst.json", + "itf_file": "/corners/Cworst.itf", + "captab_file": "/corners/Cworst.captab", + "spef_file": [ + { "-40" : "/RCX_ecc/output/gcd_Cworst_m40C.spef" }, + { "125" : "/RCX_ecc/output/gcd_Cworst_125C.spef" } + ] } ] } \ No newline at end of file diff --git a/chipcompiler/tools/ecc/configs/sta.json b/chipcompiler/tools/ecc/configs/sta.json new file mode 100644 index 00000000..2a734ab0 --- /dev/null +++ b/chipcompiler/tools/ecc/configs/sta.json @@ -0,0 +1,53 @@ +{ + "liberty" :[ + { + "corner": "MAX", + "temperature": 125, + "path" : [ + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib", + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib" + ] + }, + { + "corner": "WCL", + "temperature": -40, + "path" : [ + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_cworst_1p08_m40_nldm.lib", + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ss_cworst_1p08_m40_nldm.lib" + ] + }, + { + "corner": "TYP", + "temperature": 25, + "path" : [ + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib", + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib" + ] + }, + { + "corner": "MIN", + "temperature": -40, + "path" : [ + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ff_rcbest_1p32_m40_nldm.lib", + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ff_rcbest_1p32_m40_nldm.lib" + ] + }, + { + "corner": "ML", + "temperature": 125, + "path" : [ + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ff_cbest_1p32_125_nldm.lib", + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ff_cbest_1p32_125_nldm.lib" + ] + } + ], + "signoff" : [ + { + "MAX": ["Cworst", "RCworst"], + "WCL": ["Cworst", "RCworst"], + "TYP": ["TYPICAL"], + "MIN": ["Cworst", "RCworst", "Cbest", "RCbest"], + "ML": ["Cworst", "RCworst", "Cbest", "RCbest"] + } + ] +} \ No newline at end of file From d6a84aed03c649748436030f485e6a3339bc845e Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Fri, 29 May 2026 17:13:29 +0800 Subject: [PATCH 06/14] update rcx and signoff sta report flow --- chipcompiler/cli/workspace/config_view.py | 2 +- chipcompiler/data/workspace.py | 25 ++- chipcompiler/tools/ecc/builder.py | 14 +- chipcompiler/tools/ecc/module.py | 12 + chipcompiler/tools/ecc/runner.py | 255 ++++++++++++++++------ test/cli/test_cli_inspect.py | 23 +- 6 files changed, 260 insertions(+), 71 deletions(-) diff --git a/chipcompiler/cli/workspace/config_view.py b/chipcompiler/cli/workspace/config_view.py index d743cdd9..87928a39 100644 --- a/chipcompiler/cli/workspace/config_view.py +++ b/chipcompiler/cli/workspace/config_view.py @@ -29,7 +29,7 @@ "to_default_config_setup.json", ), ("rcx", "ecc"): ("flow_config.json", "db_default_config.json", "rcx.json"), - ("sta", "ecc"): ("flow_config.json", "db_default_config.json", "rcx.json"), + ("sta", "ecc"): ("flow_config.json", "db_default_config.json", "rcx.json", "sta.json"), ("placement", "dreamplace"): ("dreamplace.json",), ("legalization", "dreamplace"): ("dreamplace.json",), } diff --git a/chipcompiler/data/workspace.py b/chipcompiler/data/workspace.py index e7ec74d1..52686271 100644 --- a/chipcompiler/data/workspace.py +++ b/chipcompiler/data/workspace.py @@ -120,6 +120,7 @@ def build_workspace_config_paths(workspace: Workspace) -> dict: f"{StepEnum.LEGALIZATION.value}": f"{config_dir}/pl_default_config.json", f"{StepEnum.FILLER.value}": f"{config_dir}/pl_default_config.json", f"{StepEnum.RCX.value}": f"{config_dir}/rcx.json", + f"{StepEnum.STA.value}": f"{config_dir}/sta.json", "dreamplace": f"{config_dir}/dreamplace.json", } @@ -160,6 +161,18 @@ def _copy_missing_files(src_dir: str, dst_dir: str): shutil.copy2(src, dst) +def _rcx_temperature_key(temperature) -> str: + try: + numeric = float(temperature) + return str(int(numeric)) if numeric.is_integer() else f"{numeric:.12g}" + except (TypeError, ValueError): + return str(temperature) + + +def _rcx_temperature_token(temperature) -> str: + return _rcx_temperature_key(temperature).replace(".", "p").replace("-", "m") + "C" + + def init_workspace_config(workspace: Workspace) -> None: """Create workspace-level configs and write static fields once.""" import os @@ -283,7 +296,17 @@ def update_step_config(workspace: Workspace, step: WorkspaceStep) -> None: for corner in rcx.get("corners", []): corner_name = corner.get("name", "") if corner_name: - corner["spef_file"] = f"{rcx_output_dir}/{workspace.design.name}_{corner_name}.spef" + temperatures = corner.get("temperature", [25]) or [25] + corner["spef_file"] = [ + { + _rcx_temperature_key(temperature): ( + f"{rcx_output_dir}/" + f"{workspace.design.name}_{corner_name}_" + f"{_rcx_temperature_token(temperature)}.spef" + ) + } + for temperature in temperatures + ] json_write(workspace.config[f"{StepEnum.RCX.value}"], rcx) diff --git a/chipcompiler/tools/ecc/builder.py b/chipcompiler/tools/ecc/builder.py index 116fa67e..7075b214 100644 --- a/chipcompiler/tools/ecc/builder.py +++ b/chipcompiler/tools/ecc/builder.py @@ -210,7 +210,17 @@ def build_step_config(workspace: Workspace, from chipcompiler.utility import json_read rcx_config = json_read(workspace.config[f"{StepEnum.RCX.value}"]) step.output["spef"] = [ - corner.get("spef_file", "") + spef_path for corner in rcx_config.get("corners", []) - if corner.get("spef_file", "") + for spef_item in ( + corner.get("spef_file", []) + if isinstance(corner.get("spef_file", []), list) + else [corner.get("spef_file", "")] + ) + for spef_path in ( + spef_item.values() + if isinstance(spef_item, dict) + else [spef_item] + ) + if spef_path ] diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index 385ee324..c1154ff1 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -92,6 +92,18 @@ def update_step_paths(self, output_dir: str, feature_dir: str): output_path=output_dir, feature_path=feature_dir, ) + + def update_sta_data_config(self, + db_config: str, + output_dir: str, + lib_paths: list[str], + sdc_path: str): + self.ecc.db_init( + config_path=db_config, + output_path=output_dir, + lib_paths=lib_paths, + sdc_path=sdc_path, + ) ######################################################################## # data api diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index 8a7790cb..2f6670ad 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -2,6 +2,7 @@ # -*- encoding: utf-8 -*- import sys import os +import re from chipcompiler.data import WorkspaceStep, Workspace, StateEnum, StepEnum from chipcompiler.tools.ecc.module import ECCToolsModule @@ -107,7 +108,8 @@ def get_eda_instance(workspace: Workspace, def save_data(workspace: Workspace, step: WorkspaceStep, ecc_module : ECCToolsModule, - feature_step : bool = True) -> bool: + feature_step : bool = True, + report_timing : bool = True) -> bool: """ module is ecc module from db engine, eda instacnce may initialize data from this module if module has been set @@ -127,14 +129,14 @@ def save_data(workspace: Workspace, ecc_module.report_summary(path=step.report.get("db", "")) - # report timing - ecc_module.release_sta() - ecc_module.init_sta(output_dir=step.data.get("sta", ""), - top_module=workspace.design.top_module, - lib_paths=workspace.pdk.libs, - sdc_path=workspace.pdk.sdc) - ecc_module.report_timing() - ecc_module.release_sta() + if report_timing: + ecc_module.release_sta() + ecc_module.init_sta(output_dir=step.data.get("sta", ""), + top_module=workspace.design.top_module, + lib_paths=workspace.pdk.libs, + sdc_path=workspace.pdk.sdc) + ecc_module.report_timing() + ecc_module.release_sta() # update parameters db_json = json_read(step.feature.get("db", "")) @@ -810,13 +812,6 @@ def run_sta(workspace: Workspace, """ run sta """ - def spef_type_from_path(spef_file: str) -> str: - stem = os.path.splitext(os.path.basename(spef_file))[0] - design_prefix = f"{workspace.design.name}_" - if stem.startswith(design_prefix): - stem = stem[len(design_prefix):] - return stem - def safe_dir_name(name: str) -> str: value = "".join( char if char.isalnum() or char in ("-", "_", ".") else "_" @@ -824,25 +819,129 @@ def safe_dir_name(name: str) -> str: ) return value or "spef" - def collect_spef_files() -> list[tuple[str, str]]: - spef_files = step.output.get("spef", []) - if spef_files: - return [ - (safe_dir_name(spef_type_from_path(spef_file)), spef_file) - for spef_file in spef_files - ] - - config = json_read(workspace.config.get(StepEnum.RCX.value, "")) - spef_items = [] - for corner in config.get("corners", []): - spef_file = corner.get("spef_file", "") - if not spef_file: + def temperature_key(temperature) -> str: + try: + numeric = float(temperature) + if numeric.is_integer(): + return str(int(numeric)) + except (TypeError, ValueError): + pass + return str(temperature) + + def temperature_token(temperature) -> str: + return temperature_key(temperature).replace("-", "m").replace(".", "p") + + def resolve_config_path(path: str) -> str: + if path.startswith("/"): + relative = path[1:] + workspace_prefixes = ( + "CTS_ecc", "Floorplan_ecc", "fixFanout_ecc", "place_ecc", + "legalization_ecc", "route_ecc", "drc_ecc", "filler_ecc", + "RCX_ecc", "rcx_ecc", "sta_ecc", "harden_ecc", "config", + "origin", "home", + ) + pdk_prefixes = ("IP", "prtech", "corners") + prefix = relative.split("/", 1)[0] + if prefix in workspace_prefixes: + return os.path.join(workspace.directory, relative) + if prefix in pdk_prefixes: + return os.path.join(workspace.pdk.root, relative) + return path + + def normalize_spef_path(spef_file: str) -> str: + if not spef_file or os.path.exists(spef_file): + return spef_file + + dirname = os.path.dirname(spef_file) + tail = os.path.basename(spef_file) + parts = tail.rsplit("_M", 1) + if len(parts) == 2 and parts[1].endswith("C.spef"): + normalized = os.path.join(dirname, f"{parts[0]}_m{parts[1]}") + if os.path.exists(normalized): + return normalized + return spef_file + + def normalize_liberty_path(liberty_file: str) -> str: + if not liberty_file or os.path.exists(liberty_file): + return liberty_file + + liberty_dir = os.path.dirname(liberty_file) + cell_dir = os.path.basename(os.path.dirname(liberty_dir)) + tail = os.path.basename(liberty_file) + match = re.match(r"^(ics55_LLSC_H7C[0-9A-Za-z]+)(_.+)$", tail) + if match: + normalized = os.path.join(liberty_dir, f"{cell_dir}{match.group(2)}") + if os.path.exists(normalized): + return normalized + return liberty_file + + def find_liberty_corner(sta_data: dict, corner_name: str) -> dict | None: + for liberty in sta_data.get("liberty", []): + if liberty.get("corner") == corner_name: + return liberty + return None + + def find_rcx_corner(rcx_data: dict, rcx_corner_name: str) -> dict | None: + for corner in rcx_data.get("corners", []): + if corner.get("name") == rcx_corner_name: + return corner + return None + + def find_spef_for_temp(rcx_corner: dict, temperature) -> str: + spef_file = rcx_corner.get("spef_file", "") + if isinstance(spef_file, str): + return spef_file + + temp_key = temperature_key(temperature) + for spef_item in spef_file: + if not isinstance(spef_item, dict): continue + for spef_temperature, spef_path in spef_item.items(): + if temperature_key(spef_temperature) == temp_key: + return spef_path + return "" + + def collect_signoff_items() -> list[dict]: + sta_config = workspace.config.get(StepEnum.STA.value, "") + sta_data = json_read(sta_config) + rcx_data = json_read(workspace.config.get(StepEnum.RCX.value, "")) + items = [] + + for signoff_group in sta_data.get("signoff", []): + for corner_name, rcx_corner_names in signoff_group.items(): + liberty = find_liberty_corner(sta_data, corner_name) + if liberty is None: + workspace.logger.error("No liberty corner '%s' found in %s", + corner_name, + sta_config) + return [] + + temperature = liberty.get("temperature") + liberty_files = [ + normalize_liberty_path(resolve_config_path(path)) + for path in liberty.get("path", []) + ] - spef_type = corner.get("name", "") or spef_type_from_path(spef_file) - spef_items.append((safe_dir_name(spef_type), spef_file)) + for rcx_corner_name in rcx_corner_names: + rcx_corner = find_rcx_corner(rcx_data, rcx_corner_name) + if rcx_corner is None: + workspace.logger.error("No RCX corner '%s' found in %s", + rcx_corner_name, + workspace.config.get(StepEnum.RCX.value, "")) + return [] - return spef_items + spef_file = normalize_spef_path( + resolve_config_path(find_spef_for_temp(rcx_corner, temperature)) + ) + items.append({ + "corner": corner_name, + "temperature": temperature, + "rcx_corner": rcx_corner_name, + "liberty_files": liberty_files, + "spef_file": spef_file, + }) + + return items result = False @@ -856,61 +955,89 @@ def collect_spef_files() -> list[tuple[str, str]]: if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - spef_items = collect_spef_files() - if len(spef_items) <= 0: - workspace.logger.error("No SPEF files found for STA") + signoff_items = collect_signoff_items() + if len(signoff_items) <= 0: + workspace.logger.error("No signoff STA items found") + sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, + state=StateEnum.Imcomplete) + return False + + if not os.path.exists(workspace.pdk.sdc): + workspace.logger.error("STA SDC does not exist: %s", + workspace.pdk.sdc) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Imcomplete) return False - for spef_type, spef_file in spef_items: + for signoff_item in signoff_items: + corner_name = signoff_item["corner"] + temperature = signoff_item["temperature"] + rcx_corner_name = signoff_item["rcx_corner"] + liberty_files = signoff_item["liberty_files"] + spef_file = signoff_item["spef_file"] + if not os.path.exists(spef_file): - workspace.logger.error("SPEF file does not exist: %s", spef_file) - sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, - state=StateEnum.Imcomplete) - return False - if not os.path.exists(step.input.get("verilog", "")): - workspace.logger.error("STA netlist does not exist: %s", - step.input.get("verilog", "")) - sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, - state=StateEnum.Imcomplete) - return False - if len(workspace.pdk.libs) <= 0 \ - or any(not os.path.exists(lib_path) for lib_path in workspace.pdk.libs): - workspace.logger.error("STA liberty file does not exist: %s", - workspace.pdk.libs) + workspace.logger.error( + "STA SPEF does not exist for %s/%s at %sC: %s", + corner_name, + rcx_corner_name, + temperature, + spef_file, + ) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Imcomplete) return False - if not os.path.exists(workspace.pdk.sdc): - workspace.logger.error("STA SDC does not exist: %s", - workspace.pdk.sdc) + + if len(liberty_files) <= 0 or any(not os.path.exists(lib_path) for lib_path in liberty_files): + workspace.logger.error( + "STA liberty does not exist for %s: %s", + corner_name, + liberty_files, + ) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Imcomplete) return False - report_dir = os.path.join(step.output.get("dir", ""), spef_type) + report_corner_dir = f"{corner_name}_{temperature_token(temperature)}" + report_dir = os.path.join( + step.output.get("dir", ""), + safe_dir_name(report_corner_dir), + safe_dir_name(rcx_corner_name), + ) os.makedirs(report_dir, exist_ok=True) try: - ecc_module.set_design_workspace(report_dir) - ecc_module.read_netlist(step.input.get("verilog", "")) - ecc_module.read_liberty(workspace.pdk.libs) - ecc_module.link_design(workspace.design.top_module) - ecc_module.read_sdc(workspace.pdk.sdc) + ecc_module.update_sta_data_config( + db_config=workspace.config.get("db", ""), + output_dir=step.output.get("dir", ""), + lib_paths=liberty_files, + sdc_path=workspace.pdk.sdc, + ) + ecc_module.release_sta() + ecc_module.init_sta(output_dir=report_dir, + top_module=workspace.design.top_module, + lib_paths=liberty_files, + sdc_path=workspace.pdk.sdc) ecc_module.read_spef(file_name=spef_file) ecc_module.report_timing() finally: - # release sta ecc_module.release_sta() - workspace.logger.info("STA report for %s saved to %s", - spef_file, - report_dir) + workspace.logger.info( + "STA report for %s/%s at %sC saved to %s", + corner_name, + rcx_corner_name, + temperature, + report_dir, + ) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Success) - result = save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) + result = save_data(workspace=workspace, + step=step, + ecc_module=ecc_module, + feature_step=False, + report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) diff --git a/test/cli/test_cli_inspect.py b/test/cli/test_cli_inspect.py index 33d3b651..2154aaea 100644 --- a/test/cli/test_cli_inspect.py +++ b/test/cli/test_cli_inspect.py @@ -652,7 +652,7 @@ def test_config_workspace_backed_ecc_steps(self, tmp_path, capsys): ] assert all(item["source"] == "workspace_config" for item in data["records"]) - def test_config_sta_uses_rcx_workspace_config(self, tmp_path, capsys): + def test_config_sta_uses_rcx_and_sta_workspace_configs(self, tmp_path, capsys): project_dir = _create_valid_project(tmp_path) run_dir = os.path.join(project_dir, "runs", "default") _create_flow_json( @@ -667,7 +667,15 @@ def test_config_sta_uses_rcx_workspace_config(self, tmp_path, capsys): ], ) _create_step_dir(run_dir, "STA", "ecc", subdirs=["output"]) - _create_ecc_workspace_config(run_dir, "rcx.json") + _create_workspace_config( + run_dir, + { + "flow_config.json": "{}", + "db_default_config.json": "{}", + "rcx.json": "{}", + "sta.json": "{}", + }, + ) rc = cli_main.run(["config", "sta", "--resolved", "--json", "--project", project_dir]) assert rc == 0 @@ -676,6 +684,7 @@ def test_config_sta_uses_rcx_workspace_config(self, tmp_path, capsys): "runs/default/config/flow_config.json", "runs/default/config/db_default_config.json", "runs/default/config/rcx.json", + "runs/default/config/sta.json", ] assert all(item["source"] == "workspace_config" for item in data["records"]) @@ -1080,7 +1089,15 @@ def test_diagnose_sta_uses_rcx_workspace_config(self, tmp_path, capsys): "analysis/STA_metrics.json": "{}", }, ) - _create_ecc_workspace_config(run_dir, "rcx.json") + _create_workspace_config( + run_dir, + { + "flow_config.json": "{}", + "db_default_config.json": "{}", + "rcx.json": "{}", + "sta.json": "{}", + }, + ) rc = cli_main.run(["diagnose", "sta", "--project", project_dir]) assert rc == 0 From c69b0982b4f6a2dc731878ee7c1254ea19e6663f Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 2 Jun 2026 09:53:08 +0800 Subject: [PATCH 07/14] add soc flow and update floorplan init area parameters --- chipcompiler/thirdparty/ecc-dreamplace | 2 +- chipcompiler/thirdparty/ecc-tools | 2 +- chipcompiler/tools/ecc/runner.py | 53 ++++++++++--------- chipcompiler/tools/yosys/builder.py | 43 ++++++++++----- test/test_soc.py | 73 ++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 40 deletions(-) create mode 100644 test/test_soc.py diff --git a/chipcompiler/thirdparty/ecc-dreamplace b/chipcompiler/thirdparty/ecc-dreamplace index 23f48c06..1e9d0388 160000 --- a/chipcompiler/thirdparty/ecc-dreamplace +++ b/chipcompiler/thirdparty/ecc-dreamplace @@ -1 +1 @@ -Subproject commit 23f48c063f8dc65c9b0b2e2627cee689ddbb2dc2 +Subproject commit 1e9d038870efa0141b6122e120456c7eb05c8fee diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index 2f243466..e2e2bc56 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit 2f24346665fb9f76066fc953d0c4c75523e38bbe +Subproject commit e2e2bc56d968cbec02891be711032fd76481d27b diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index 2f6670ad..5878c54c 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -17,7 +17,7 @@ def create_db_engine(workspace: Workspace, step: WorkspaceStep) -> ECCToolsModule: """""" - def load_data(): + def load_data(): ecc_module = ECCToolsModule() ecc_module.init_config(flow_config=workspace.config.get("flow"), @@ -154,11 +154,11 @@ def save_data(workspace: Workspace, update_param = { "Die": { - "Size": [die_bounding_width, die_bounding_height], + "Size": f"0, 0, {die_bounding_width}, {die_bounding_height}", "Area": die_area }, "Core": { - "Size": [core_bounding_width, core_bounding_height], + "Size": f"0, 0, {core_bounding_width}, {core_bounding_height}", "Area": core_area, "Bounding box": "({} , {}) ({} , {})".format( margin[0], @@ -437,6 +437,7 @@ def run_routing(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) if ecc_module.is_rt_timing_enable(config=workspace.config.get(f"{StepEnum.ROUTING.value}", "")): + ecc_module.release_sta() ecc_module.init_sta(output_dir=step.data.get(f"{StepEnum.ROUTING.value}", ""), top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, @@ -576,28 +577,31 @@ def run_floorplan(workspace: Workspace, state=StateEnum.Success) # init floorplan - # init by core utilization - util = workspace.parameters.data.get("Core", {}).get("Utilitization", 0.3) - margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) - aspect_ratio = workspace.parameters.data.get("Core", {}).get("Aspect ratio", 1) - ecc_module.init_floorplan_by_core_utilization( - core_site=workspace.pdk.site_core, - io_site=workspace.pdk.site_io, - corner_site=workspace.pdk.site_corner, - core_util=util, - x_margin=margin[0], - y_margin=margin[1], - aspect_ratio=aspect_ratio, - ) + - # init by die and core area - # die_area=workspace.parameters.data.get("Die", {}).get("Bounding box", "") - # core_area=workspace.parameters.data.get("Core", {}).get("Bounding box", "") - # ecc_module.init_floorplan_by_area(die_area=die_area, - # core_area=core_area, - # core_site=workspace.pdk.site_core, - # io_site=workspace.pdk.site_io, - # corner_site=workspace.pdk.site_corner) + die_area=workspace.parameters.data.get("Die", {}).get("Size", "") + core_area=workspace.parameters.data.get("Core", {}).get("Size", "") + if len(die_area) == 4 and len(core_area) == 4: + # init by die and core area + ecc_module.init_floorplan_by_area(die_area=die_area, + core_area=core_area, + core_site=workspace.pdk.site_core, + io_site=workspace.pdk.site_io, + corner_site=workspace.pdk.site_corner) + else: + # init by core utilization + util = workspace.parameters.data.get("Core", {}).get("Utilitization", 0.3) + margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) + aspect_ratio = workspace.parameters.data.get("Core", {}).get("Aspect ratio", 1) + ecc_module.init_floorplan_by_core_utilization( + core_site=workspace.pdk.site_core, + io_site=workspace.pdk.site_io, + corner_site=workspace.pdk.site_corner, + core_util=util, + x_margin=margin[0], + y_margin=margin[1], + aspect_ratio=aspect_ratio, + ) sub_flow.update_step(step_name=EccSubFlowEnum.init_floorplan.value, state=StateEnum.Success) @@ -740,6 +744,7 @@ def run_harden(workspace: Workspace, ecc_module = ecc_module) if ecc_module is not None: + ecc_module.release_sta() ecc_module.init_sta(output_dir=step.data["sta"], top_module=workspace.design.top_module, lib_paths=workspace.pdk.libs, diff --git a/chipcompiler/tools/yosys/builder.py b/chipcompiler/tools/yosys/builder.py index 5cdd6b1e..98c331b8 100644 --- a/chipcompiler/tools/yosys/builder.py +++ b/chipcompiler/tools/yosys/builder.py @@ -3,6 +3,7 @@ import os import stat from chipcompiler.data import WorkspaceStep, Workspace +from chipcompiler.utility import json_read def _tcl_quote(value: str) -> str: @@ -22,19 +23,33 @@ def _abspath(path: str) -> str: return os.path.abspath(path) -def _split_stdcell_macro_libs(lib_files: list[str]) -> tuple[list[str], list[str]]: - """Split known ICS55 stdcell libraries from macro libraries.""" - stdcell_libs = [] - macro_libs = [] +def _existing_unique_paths(paths: list[str]) -> list[str]: + """Return existing paths in input order without duplicates.""" + unique_paths = [] + seen = set() - for lib in lib_files: - name = os.path.basename(lib) - if name.startswith("ICS55_"): - macro_libs.append(lib) - else: - stdcell_libs.append(lib) + for path in paths: + if not path or not os.path.isfile(path): + continue + abs_path = os.path.abspath(path) + if abs_path in seen: + continue + unique_paths.append(abs_path) + seen.add(abs_path) - return stdcell_libs, macro_libs + return unique_paths + + +def _workspace_libs(workspace: Workspace) -> list[str]: + """Read extra liberty files from the workspace DB config.""" + db_config = json_read(workspace.config.get("db", "")) + lib_paths = db_config.get("INPUT", {}).get("lib_path", []) + if isinstance(lib_paths, str): + lib_paths = [lib_paths] + if not isinstance(lib_paths, list): + return [] + + return _existing_unique_paths(lib_paths) def generate_global_var_tcl(workspace: Workspace, @@ -84,9 +99,9 @@ def generate_global_var_tcl(workspace: Workspace, abc_driver_cell = pdk.abc_driver_cell if pdk.abc_driver_cell else "BUFX4H7L" abc_load = pdk.abc_load if pdk.abc_load else 0.015 - lib_files = workspace.pdk.libs if workspace.pdk.libs else [] - lib_stdcell, lib_macro = _split_stdcell_macro_libs(lib_files) - lib_files_all = " ".join(lib_stdcell + lib_macro) if lib_files else "" + lib_stdcell = _existing_unique_paths(workspace.pdk.libs if workspace.pdk.libs else []) + lib_files = _existing_unique_paths(lib_stdcell + _workspace_libs(workspace)) + lib_files_all = " ".join(lib_files) if lib_files else "" lib_stdcell_all = " ".join(lib_stdcell) if lib_stdcell else "" filelist = workspace.design.input_filelist if workspace.design.input_filelist else workspace.parameters.data.get("File list", "") diff --git a/test/test_soc.py b/test/test_soc.py new file mode 100644 index 00000000..ab2a7e60 --- /dev/null +++ b/test/test_soc.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import sys +import os + +current_dir = os.path.split(os.path.abspath(__file__))[0] +root = current_dir.rsplit('/', 1)[0] +sys.path.append(root) + +from chipcompiler.data import ( + create_workspace, + load_workspace, + log_workspace, + log_parameters, + StepEnum, + StateEnum, + get_pdk, + get_design_parameters +) + +from chipcompiler.engine import ( + EngineDB, + EngineFlow +) + +def test_ics55_soc(): + workspace_dir="{}/test/examples/soc".format(root) + + workspace = load_workspace(workspace_dir) + + from chipcompiler.engine import EngineDB + # build engine_db for workspace + engine_db = EngineDB(workspace=workspace) + # build engine flow for workspace + engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) + if not engine_flow.has_init(): + from chipcompiler.rtl2gds import build_rtl2gds_flow + steps = build_rtl2gds_flow() + for step, tool, state in steps: + engine_flow.add_step(step=step, tool=tool, state=state) + + engine_flow.create_step_workspaces() + + engine_flow.run_steps() + +def test_ics55_core(): + workspace_dir="{}/test/examples/core".format(root) + + workspace = load_workspace(workspace_dir) + + from chipcompiler.engine import EngineDB + # build engine_db for workspace + engine_db = EngineDB(workspace=workspace) + # build engine flow for workspace + engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) + if not engine_flow.has_init(): + from chipcompiler.rtl2gds import build_rtl2gds_flow + steps = build_rtl2gds_flow() + for step, tool, state in steps: + engine_flow.add_step(step=step, tool=tool, state=state) + + engine_flow.create_step_workspaces() + + engine_flow.run_steps() + + +if __name__ == "__main__": + test_ics55_soc() + + test_ics55_core() + + exit(0) \ No newline at end of file From 2d66bfaab440afe69ffc0f05358c41ef72a160b8 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 2 Jun 2026 10:09:16 +0800 Subject: [PATCH 08/14] merge --- chipcompiler/thirdparty/ecc-dreamplace | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chipcompiler/thirdparty/ecc-dreamplace b/chipcompiler/thirdparty/ecc-dreamplace index 23f48c06..1e9d0388 160000 --- a/chipcompiler/thirdparty/ecc-dreamplace +++ b/chipcompiler/thirdparty/ecc-dreamplace @@ -1 +1 @@ -Subproject commit 23f48c063f8dc65c9b0b2e2627cee689ddbb2dc2 +Subproject commit 1e9d038870efa0141b6122e120456c7eb05c8fee From 032645e166215c3ec23676e5f46af698a6931c23 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Mon, 8 Jun 2026 16:34:10 +0800 Subject: [PATCH 09/14] update ecc flow --- chipcompiler/data/workspace.py | 9 +++ chipcompiler/engine/flow.py | 41 ++++++++++++-- chipcompiler/thirdparty/ecc-dreamplace | 2 +- chipcompiler/thirdparty/ecc-tools | 2 +- chipcompiler/tools/ecc/runner.py | 78 ++++++++++++++++---------- test/test_soc.py | 4 +- 6 files changed, 96 insertions(+), 40 deletions(-) diff --git a/chipcompiler/data/workspace.py b/chipcompiler/data/workspace.py index 52686271..b199b5af 100644 --- a/chipcompiler/data/workspace.py +++ b/chipcompiler/data/workspace.py @@ -613,6 +613,15 @@ def load_workspace(directory : str) -> Workspace: if len(spef_path) > 0: pdk.spef = spef_path[0] + # update lef and lib paths based on config + from chipcompiler.utility import json_read + db_json = json_read(workspace.config.get("db", "")) + if db_json.get("INPUT", {}).get("tech_lef_path", "") != "": + pdk.tech = db_json.get("INPUT", {}).get("tech_lef_path", "") + if db_json.get("INPUT", {}).get("lef_paths", []) != []: + pdk.lefs = db_json.get("INPUT", {}).get("lef_paths", []) + if db_json.get("INPUT", {}).get("lib_path", []) != []: + pdk.libs = db_json.get("INPUT", {}).get("lib_path", []) workspace.pdk = pdk #update config diff --git a/chipcompiler/engine/flow.py b/chipcompiler/engine/flow.py index 6e61cdf5..b0a40c9f 100644 --- a/chipcompiler/engine/flow.py +++ b/chipcompiler/engine/flow.py @@ -5,16 +5,35 @@ import time import logging import traceback -from multiprocessing import Process -from threading import Thread +from threading import Event, Thread from chipcompiler.data import Workspace, WorkspaceStep, StateEnum, StepEnum, log_flow from chipcompiler.engine import EngineDB -from chipcompiler.utility import track_process_memory from chipcompiler.utility.log import redirect_stdio_to_file logger = logging.getLogger(__name__) +def get_process_rss_mb(pid : int) -> float: + peak_memory = 0 + try: + with open(f"/proc/{pid}/status", 'r') as f: + for line in f: + if line.startswith("VmRSS:"): + rss_kb = int(line.split()[1]) + peak_memory = rss_kb / 1024 + break + except Exception: + pass + return peak_memory + +def track_current_process_memory(pid : int, + stop_event : Event, + peak_memory : list[float]): + while not stop_event.is_set(): + peak_memory[0] = max(peak_memory[0], get_process_rss_mb(pid)) + stop_event.wait(0.1) + peak_memory[0] = max(peak_memory[0], get_process_rss_mb(pid)) + class EngineFlow: def __init__(self, workspace : Workspace, engine_db : EngineDB = None): self.workspace = workspace @@ -321,7 +340,15 @@ def run_step(self, step_tag = f"{workspace_step.name}({workspace_step.tool})" self.workspace.logger.info(f"[STEP] {step_tag} pid={os.getpid()} started") - + + pid = os.getpid() + start_memory_mb = get_process_rss_mb(pid) + peak_memory = [start_memory_mb] + stop_memory_monitor = Event() + memory_monitor = Thread(target=track_current_process_memory, + args=(pid, stop_memory_monitor, peak_memory), + daemon=True) + memory_monitor.start() try: from chipcompiler.tools import run_step as run_tool_step result = run_tool_step(workspace=self.workspace, step=workspace_step, ecc_module=self.engine_db.engine) @@ -329,9 +356,13 @@ def run_step(self, except Exception: self.workspace.logger.error(f"[STEP] {step_tag} failed with exception") traceback.print_exc() + finally: + stop_memory_monitor.set() + memory_monitor.join() # compute metrics - peak_memory_mb = 0 + peak_memory_mb = peak_memory[0] - start_memory_mb + peak_memory_mb = 0 if peak_memory_mb < 0 else round(peak_memory_mb, 3) elapsed = time.time() - start_time runtime = f"{int(elapsed // 3600)}:{int((elapsed % 3600) // 60)}:{int(elapsed % 60)}" diff --git a/chipcompiler/thirdparty/ecc-dreamplace b/chipcompiler/thirdparty/ecc-dreamplace index 1e9d0388..6c638fa0 160000 --- a/chipcompiler/thirdparty/ecc-dreamplace +++ b/chipcompiler/thirdparty/ecc-dreamplace @@ -1 +1 @@ -Subproject commit 1e9d038870efa0141b6122e120456c7eb05c8fee +Subproject commit 6c638fa0c369dcb53b6a851df5a65abe44c7a061 diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index bd388210..30b5161b 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit bd3882102078fcb1e77b450aa468bfe806c6e564 +Subproject commit 30b5161bdb71eb43f26c2664d1e9785c241a1c59 diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index 5878c54c..1238dd73 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -109,13 +109,13 @@ def save_data(workspace: Workspace, step: WorkspaceStep, ecc_module : ECCToolsModule, feature_step : bool = True, - report_timing : bool = True) -> bool: + report_timing : bool = False) -> bool: """ module is ecc module from db engine, eda instacnce may initialize data from this module if module has been set """ if ecc_module is None: - return FALSE + return False ecc_module.def_save(def_path=step.output.get("def", "")) ecc_module.verilog_save(output_verilog=step.output.get("verilog", "")) @@ -152,20 +152,27 @@ def save_data(workspace: Workspace, margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) + core_usage = db_json.get("Design Layout", {}).get("core_usage", 0) + + aspect_ratio = die_bounding_width / die_bounding_height if die_bounding_height > 0 else 1 + + update_param = { "Die": { - "Size": f"0, 0, {die_bounding_width}, {die_bounding_height}", + "Size": [die_bounding_width, die_bounding_height], "Area": die_area }, "Core": { - "Size": f"0, 0, {core_bounding_width}, {core_bounding_height}", + "Size": [core_bounding_width, core_bounding_height], "Area": core_area, "Bounding box": "({} , {}) ({} , {})".format( margin[0], margin[1], core_bounding_width + margin[0], core_bounding_height + margin[1] - ) + ), + "Utilitization": core_usage, + "Aspect ratio": aspect_ratio } } @@ -307,7 +314,7 @@ def run_placement(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_placement.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -447,7 +454,7 @@ def run_routing(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_routing.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -482,7 +489,7 @@ def run_drc(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_DRC.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False) ecc_module.save_drc(feature_path=step.feature.get("step", "")) @@ -518,7 +525,7 @@ def run_legalization(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_legalization.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -549,7 +556,7 @@ def run_filler(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.run_filler.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -577,22 +584,13 @@ def run_floorplan(workspace: Workspace, state=StateEnum.Success) # init floorplan - - - die_area=workspace.parameters.data.get("Die", {}).get("Size", "") - core_area=workspace.parameters.data.get("Core", {}).get("Size", "") - if len(die_area) == 4 and len(core_area) == 4: - # init by die and core area - ecc_module.init_floorplan_by_area(die_area=die_area, - core_area=core_area, - core_site=workspace.pdk.site_core, - io_site=workspace.pdk.site_io, - corner_site=workspace.pdk.site_corner) - else: + die_area=workspace.parameters.data.get("Die", {}).get("Size", []) + core_area=workspace.parameters.data.get("Core", {}).get("Size", []) + util = workspace.parameters.data.get("Core", {}).get("Utilitization", 0.3) + margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) + aspect_ratio = workspace.parameters.data.get("Core", {}).get("Aspect ratio", 1) + if len(die_area) == 0: # init by core utilization - util = workspace.parameters.data.get("Core", {}).get("Utilitization", 0.3) - margin = workspace.parameters.data.get("Core", {}).get("Margin", [0, 0]) - aspect_ratio = workspace.parameters.data.get("Core", {}).get("Aspect ratio", 1) ecc_module.init_floorplan_by_core_utilization( core_site=workspace.pdk.site_core, io_site=workspace.pdk.site_io, @@ -602,6 +600,23 @@ def run_floorplan(workspace: Workspace, y_margin=margin[1], aspect_ratio=aspect_ratio, ) + else: + # init by die and core area + if len(die_area) != 2 or len(margin) != 2: + return reslut + + str_die = f"0, 0, {die_area[0]}, {die_area[1]}" + + if len(core_area) == 2: + str_core = f"{margin[0]}, {margin[1]}, {core_area[0]+margin[0]}, {core_area[1]+margin[1]}" + else: + str_core = f"{margin[0]}, {margin[1]}, {die_area[0]-margin[0]}, {die_area[1]-margin[1]}" + + ecc_module.init_floorplan_by_area(die_area=str_die, + core_area=str_core, + core_site=workspace.pdk.site_core, + io_site=workspace.pdk.site_io, + corner_site=workspace.pdk.site_corner) sub_flow.update_step(step_name=EccSubFlowEnum.init_floorplan.value, state=StateEnum.Success) @@ -658,10 +673,11 @@ def run_floorplan(workspace: Workspace, # auto place io pins json_iopin_place = json_floorplan.get("Auto place pin", {}) - ecc_module.auto_place_pins(layer=json_iopin_place.get("layer", ""), - width=json_iopin_place.get("width", 0), - height=json_iopin_place.get("height", 0), - sides=json_iopin_place.get("sides", [])) + if len(json_iopin_place) > 0: + ecc_module.auto_place_pins(layer=json_iopin_place.get("layer", ""), + width=json_iopin_place.get("width", 0), + height=json_iopin_place.get("height", 0), + sides=json_iopin_place.get("sides", [])) sub_flow.update_step(step_name=EccSubFlowEnum.place_io_pins.value, state=StateEnum.Success) @@ -719,7 +735,7 @@ def run_floorplan(workspace: Workspace, sub_flow.update_step(step_name=EccSubFlowEnum.set_clock_net.value, state=StateEnum.Success) - reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) + reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) @@ -801,7 +817,7 @@ def run_jsons_to_itf(ecc_module : ECCToolsModule) -> bool: ecc_module.report_rcx() sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Success) - save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False) + save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False, report_timing=False) sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success) diff --git a/test/test_soc.py b/test/test_soc.py index ab2a7e60..26c1bb74 100644 --- a/test/test_soc.py +++ b/test/test_soc.py @@ -55,8 +55,8 @@ def test_ics55_core(): # build engine flow for workspace engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) if not engine_flow.has_init(): - from chipcompiler.rtl2gds import build_rtl2gds_flow - steps = build_rtl2gds_flow() + from chipcompiler.rtl2gds import build_harden_flow + steps = build_harden_flow() for step, tool, state in steps: engine_flow.add_step(step=step, tool=tool, state=state) From f84c46940555468eeef8cf383d988cf5513d8982 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Mon, 8 Jun 2026 17:09:43 +0800 Subject: [PATCH 10/14] merge main --- chipcompiler/thirdparty/ecc-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index e6050a57..30b5161b 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit e6050a57e48f7c4567552d6ba2f86958d63d8d51 +Subproject commit 30b5161bdb71eb43f26c2664d1e9785c241a1c59 From 596d9e26327963c567314d969e48ba261369a97a Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 9 Jun 2026 20:40:56 +0800 Subject: [PATCH 11/14] update rcx flow by rcx_ics55.so --- chipcompiler/data/pdk.py | 27 +- chipcompiler/data/workspace.py | 24 +- chipcompiler/tools/ecc/configs/rcx.json | 60 +- chipcompiler/tools/ecc/configs/sta.json | 10 +- chipcompiler/tools/ecc/module.py | 867 +----------------------- chipcompiler/tools/ecc/runner.py | 314 ++++----- 6 files changed, 149 insertions(+), 1153 deletions(-) diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index 2213e2b0..839002ad 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -90,47 +90,32 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: "{}/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib".format(stdcell_dir), "{}/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ss_rcworst_1p08_125_nldm.lib".format(stdcell_dir) ] - mapping_file = "{}/corners/ICsprout_55LLULP_1P6M_5lc_V1p1_cell.map".format(resolved_root) + mapping_file = "" corners = [ { "name" : "TYPICAL", "temperature" : [25], - "ecc_tf" : "{}/corners/TYP.json".format(resolved_root), - "itf_file" : "{}/corners/TYP.itf".format(resolved_root), - "captab_file" : "{}/corners/TYP.captab".format(resolved_root), - "spef_file" : "{}/corners/TYP.spef".format(resolved_root) + "spef_file" : "./TYP.spef" }, { "name" : "RCbest", "temperature" : [-40, 125], - "ecc_tf" : "{}/corners/RCbest.json".format(resolved_root), - "itf_file" : "{}/corners/RCbest.itf".format(resolved_root), - "captab_file" : "{}/corners/RCbest.captab".format(resolved_root), - "spef_file" : "{}/corners/RCbest.spef".format(resolved_root) + "spef_file" : "./RCbest.spef" }, { "name" : "RCworst", "temperature" : [-40, 125], - "ecc_tf" : "{}/corners/RCworst.json".format(resolved_root), - "itf_file" : "{}/corners/RCworst.itf".format(resolved_root), - "captab_file" : "{}/corners/RCworst.captab".format(resolved_root), - "spef_file" : "{}/corners/RCworst.spef".format(resolved_root) + "spef_file" : "./RCworst.spef" }, { "name" : "Cbest", "temperature" : [-40, 125], - "ecc_tf" : "{}/corners/Cbest.json".format(resolved_root), - "itf_file" : "{}/corners/Cbest.itf".format(resolved_root), - "captab_file" : "{}/corners/Cbest.captab".format(resolved_root), - "spef_file" : "{}/corners/Cbest.spef".format(resolved_root) + "spef_file" : "./Cbest.spef" }, { "name" : "Cworst", "temperature" : [-40, 125], - "ecc_tf" : "{}/corners/Cworst.json".format(resolved_root), - "itf_file" : "{}/corners/Cworst.itf".format(resolved_root), - "captab_file" : "{}/corners/Cworst.captab".format(resolved_root), - "spef_file" : "{}/corners/Cworst.spef".format(resolved_root) + "spef_file" : "./Cworst.spef" } ] diff --git a/chipcompiler/data/workspace.py b/chipcompiler/data/workspace.py index aca78b03..36cb0078 100644 --- a/chipcompiler/data/workspace.py +++ b/chipcompiler/data/workspace.py @@ -257,12 +257,24 @@ def init_workspace_config(workspace: Workspace) -> None: router["RT"]["-top_routing_layer"] = workspace.parameters.data.get("Top layer", "") json_write(workspace.config[f"{StepEnum.ROUTING.value}"], router) - rcx = json_read(workspace.config[f"{StepEnum.RCX.value}"]) - rcx["pdk"] = "ics55" if workspace.pdk.name == "ics55" else "" - rcx["mapping_file"] = workspace.pdk.mapping_file - corners = deepcopy(workspace.pdk.corners) - rcx["corners"] = corners - json_write(workspace.config[f"{StepEnum.RCX.value}"], rcx) + # rcx = json_read(workspace.config[f"{StepEnum.RCX.value}"]) + # rcx["pdk"] = "ics55" if workspace.pdk.name == "ics55" else "" + # rcx["mapping_file"] = workspace.pdk.mapping_file + # corners = deepcopy(workspace.pdk.corners) + # rcx["corners"] = corners + # json_write(workspace.config[f"{StepEnum.RCX.value}"], rcx) + + sta = json_read(workspace.config[f"{StepEnum.STA.value}"]) + pdk_root = workspace.pdk.root.rstrip(os.sep) + for liberty in sta.get("liberty", []): + liberty["path"] = [ + path + if path == pdk_root or path.startswith(f"{pdk_root}{os.sep}") + else os.path.join(workspace.pdk.root, path.lstrip(os.sep)) + for path in liberty.get("path", []) + ] + + json_write(workspace.config[f"{StepEnum.STA.value}"], sta) dreamplace = json_read(workspace.config["dreamplace"]) dreamplace["lef_input"] = [workspace.pdk.tech, *workspace.pdk.lefs] diff --git a/chipcompiler/tools/ecc/configs/rcx.json b/chipcompiler/tools/ecc/configs/rcx.json index 930b972a..65092994 100644 --- a/chipcompiler/tools/ecc/configs/rcx.json +++ b/chipcompiler/tools/ecc/configs/rcx.json @@ -1,62 +1,4 @@ { - "pdk": "", "thread_num": 64, - "output": "/RCX_ecc/output", - "mapping_file": "/corners/ICsprout_55LLULP_1P6M_5lc_V1p1_cell.map", - "corners": [ - { - "name": "TYPICAL", - "temperature": [25], - "ecc_tf": "/corners/TYP.json", - "itf_file": "/corners/TYP.itf", - "captab_file": "/corners/TYP.captab", - "spef_file": [ - { "25" : "/RCX_ecc/output/gcd_TYPICAL_25C.spef" } - ] - }, - { - "name": "RCbest", - "temperature": [-40, 125], - "ecc_tf": "/corners/RCbest.json", - "itf_file": "/corners/RCbest.itf", - "captab_file": "/corners/RCbest.captab", - "spef_file": [ - { "-40" : "/RCX_ecc/output/gcd_RCbest_m40C.spef" }, - { "125" : "/RCX_ecc/output/gcd_RCbest_125C.spef" } - ] - }, - { - "name": "RCworst", - "temperature": [-40, 125], - "ecc_tf": "/corners/RCworst.json", - "itf_file": "/corners/RCworst.itf", - "captab_file": "/corners/RCworst.captab", - "spef_file": [ - { "-40" : "/RCX_ecc/output/gcd_RCworst_m40C.spef" }, - { "125" : "/RCX_ecc/output/gcd_RCworst_125C.spef" } - ] - }, - { - "name": "Cbest", - "temperature": [-40, 125], - "ecc_tf": "/corners/Cbest.json", - "itf_file": "/corners/Cbest.itf", - "captab_file": "/corners/Cbest.captab", - "spef_file": [ - { "-40" : "/RCX_ecc/output/gcd_Cbest_m40C.spef" }, - { "125" : "/RCX_ecc/output/gcd_Cbest_125C.spef" } - ] - }, - { - "name": "Cworst", - "temperature": [-40, 125], - "ecc_tf": "/corners/Cworst.json", - "itf_file": "/corners/Cworst.itf", - "captab_file": "/corners/Cworst.captab", - "spef_file": [ - { "-40" : "/RCX_ecc/output/gcd_Cworst_m40C.spef" }, - { "125" : "/RCX_ecc/output/gcd_Cworst_125C.spef" } - ] - } - ] + "output": "/RCX_ecc/output" } diff --git a/chipcompiler/tools/ecc/configs/sta.json b/chipcompiler/tools/ecc/configs/sta.json index 2a734ab0..0293a927 100644 --- a/chipcompiler/tools/ecc/configs/sta.json +++ b/chipcompiler/tools/ecc/configs/sta.json @@ -5,7 +5,7 @@ "temperature": 125, "path" : [ "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib", - "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib" + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ss_rcworst_1p08_125_nldm.lib" ] }, { @@ -13,7 +13,7 @@ "temperature": -40, "path" : [ "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_cworst_1p08_m40_nldm.lib", - "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ss_cworst_1p08_m40_nldm.lib" + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ss_cworst_1p08_m40_nldm.lib" ] }, { @@ -21,7 +21,7 @@ "temperature": 25, "path" : [ "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib", - "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_typ_tt_1p2_25_nldm.lib" + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_typ_tt_1p2_25_nldm.lib" ] }, { @@ -29,7 +29,7 @@ "temperature": -40, "path" : [ "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ff_rcbest_1p32_m40_nldm.lib", - "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ff_rcbest_1p32_m40_nldm.lib" + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ff_rcbest_1p32_m40_nldm.lib" ] }, { @@ -37,7 +37,7 @@ "temperature": 125, "path" : [ "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ff_cbest_1p32_125_nldm.lib", - "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CR_ff_cbest_1p32_125_nldm.lib" + "/IP/STD_cell/ics55_LLSC_H7C_V1p10C100/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ff_cbest_1p32_125_nldm.lib" ] } ], diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index 8bae313c..a1a36457 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -17,7 +17,8 @@ class ECCToolsModule: """ def __init__(self): try: - from ecc_tools_bin import ecc_py as ecc + # from ecc_tools_bin import ecc_py as ecc + from chipcompiler.tools.ecc.bin import ecc_py as ecc except ImportError: try: from chipcompiler.tools.ecc.bin import ecc_py as ecc @@ -890,7 +891,7 @@ def is_rt_timing_enable(self, config : str): ######################################################################## # RCX api ######################################################################## - def init_rcx(self, config: str, pdk: str = ""): + def init_rcx(self, config: str, pdk: str = "ics55"): if pdk: return self.ecc.init_rcx(config=config, pdk=pdk) return self.ecc.init_rcx(config=config) @@ -901,9 +902,6 @@ def run_rcx(self): def report_rcx(self): return self.ecc.report_rcx() - def rcx_json_to_itf(self, json_path: str, itf_path: str): - rcx_extractor =self.RcxExtraction(json_path, itf_path) - rcx_extractor.transfer() ######################################################################## # STA api @@ -1293,861 +1291,4 @@ def update_and_get_all_pin_timings( pin_net_delay, cell_arc_delays, net_timing_details, - ) - - class RcxExtraction: - SAFE_TOKEN_RE = re.compile(r"^[^\s{}=#]+$") - - PROCESS_FIELD_MAP = { - "name": "TECHNOLOGY", - "temperature": "GLOBAL_TEMPERATURE", - "half_node_scale_factor": "HALF_NODE_SCALE_FACTOR", - } - - FIELD_MAP = { - "epsilon_ratio": "ER", - "min_width": "WMIN", - "min_spacing": "SMIN", - "btm_layer": "FROM", - "top_layer": "TO", - "res_per_via": "RPV", - "area": "AREA", - "TCR1": "CRT1", - "TCR2": "CRT2", - "layer_type": "LAYER_TYPE", - "temp_reference": "T0", - "measured_from": "MEASURED_FROM", - "top_thickness": "TW_T", - "side_thickness": "SW_T", - "damage_thickness": "DAMAGE_THICKNESS", - "damage_er": "DAMAGE_ER", - "gate_to_contact_smin": "GATE_TO_CONTACT_SMIN", - "side_tangent": "SIDE_TANGENT", - "dielectric_layer": "DIELECTRIC_LAYER", - "number_of_tables": "NUMBER_OF_TABLES", - "contact_to_contact_spacings": "CONTACT_TO_CONTACT_SPACINGS", - "gate_to_contact_spacings": "GATE_TO_CONTACT_SPACINGS", - "caps_per_micron": "CAPS_PER_MICRON", - "thickness_changes": "THICKNESS_CHANGES", - "lengths": "LENGTHS", - "widths": "WIDTHS", - "spacings": "SPACINGS", - "values": "VALUES", - "density_polynomial_orders": "DENSITY_POLYNOMIAL_ORDERS", - "width_polynomial_orders": "WIDTH_POLYNOMIAL_ORDERS", - "width_ranges": "WIDTH_RANGES", - "polynomial_coefficients": "POLYNOMIAL_COEFFICIENTS", - } - - SCALAR_ETCH_FIELDS = { - "etch_shrink": "ETCH", - "etch_shrink_c": "CAPACITIVE_ONLY_ETCH", - "etch_shrink_r": "RESISTIVE_ONLY_ETCH", - } - - CONDUCTOR_SPECIAL_BLOCKS = { - "rho", - "wire_edge_enlargement", - "wire_edge_enlargement_c", - "wire_edge_enlargement_r", - "width_dependent_tc", - "wire_thickness_ratio", - "polynomial_based_thickness_variation", - "gate_to_diffusion_cap", - "ild_vs_width_and_spacing", - } - - VIA_SPECIAL_BLOCKS = { - "contact_resistance", - "crt_vs_area", - "etch_vs_width_and_length", - "etch_vs_contact_and_gate_spacings", - } - - RESERVED_KEYS = { - "name", - "btm_height", - "top_height", - "entries", - } - - def __init__(self, input_path : str, output_path : str) -> None: - self.input_path = input_path - self.output_path = output_path - - def transfer(self): - data = self.load_json() - output = self.json_to_itf(data) - - Path(self.output_path).write_text(output) - - - @dataclass(frozen=True) - class JsonNumber: - text: str - decimal: Decimal - - - @dataclass(frozen=True) - class EntryLayout: - type_width: int - name_width: int - - - def parse_json_number(self, text: str) -> JsonNumber: - return self.JsonNumber(text, Decimal(text)) - - - def as_decimal(self,value: Any, default: Decimal = Decimal("0")) -> Decimal: - if value is None: - return default - if isinstance(value, self.JsonNumber): - return value.decimal - if isinstance(value, Decimal): - return value - if isinstance(value, int): - return Decimal(value) - if isinstance(value, float): - return Decimal(str(value)) - text = str(value).strip() - if not text: - return default - try: - return Decimal(text) - except InvalidOperation: - return default - - - def decimal_to_text(self, value: Decimal) -> str: - text = format(value, "f") - if "." in text: - text = text.rstrip("0").rstrip(".") - if text in {"", "-0", "-0.0"}: - return "0" - return text - - - def quote_token(self, value: str) -> str: - if self.SAFE_TOKEN_RE.match(value) and not value.startswith("//"): - return value - return json.dumps(value, ensure_ascii=False) - - - def format_scalar(self, value: Any) -> str: - if isinstance(value, self.JsonNumber): - return value.text - if isinstance(value, Decimal): - return self.decimal_to_text(value) - if isinstance(value, bool): - return "1" if value else "0" - if isinstance(value, int): - return str(value) - if isinstance(value, float): - if not math.isfinite(value): - raise ValueError(f"non-finite float is not valid ITF value: {value!r}") - return format(value, ".15g") - if value is None: - raise ValueError("null is not a valid ITF value") - if isinstance(value, str): - return self.quote_token(value) - return self.quote_token(str(value)) - - - def format_freeform_cell(self, value: Any) -> str: - if isinstance(value, str): - return value.strip() - return self.format_scalar(value) - - - def format_row(self, value: Any) -> str: - if isinstance(value, str): - return value.strip() - if isinstance(value, list): - return " ".join(self.format_freeform_cell(cell) for cell in value) - return self.format_freeform_cell(value) - - - def format_parenthesized_entry(self, entry: Any) -> str: - if isinstance(entry, str): - text = entry.strip() - return text if text.startswith("(") else f"({text})" - if isinstance(entry, list): - return "(" + ", ".join(self.format_freeform_cell(cell) for cell in entry) + ")" - raise ValueError(f"unsupported parenthesized entry payload: {type(entry)!r}") - - - def is_scalar_sequence(self, value: Any) -> bool: - return isinstance(value, list) and all(not isinstance(item, (list, dict)) for item in value) - - - def is_matrix_like(self, value: Any) -> bool: - return isinstance(value, list) and value and all(isinstance(item, (list, str)) for item in value) - - - def looks_like_series_text(self, value: str) -> bool: - text = value.strip() - return any(token in text for token in (" ", "\t", "\n", "(", ")")) - - - def normalized_statement_name(self, name: str) -> str: - return self.FIELD_MAP.get(name, name.upper()) - - - def emit_assignment(self, key: str, value: Any, indent: str = "") -> str: - return f"{indent}{self.normalized_statement_name(key)} = {self.format_scalar(value)}" - - - def format_assignment_pair(self, key: str, value: Any) -> str: - return f"{self.normalized_statement_name(key)} = {self.format_scalar(value)}" - - - def entry_prefix(self, kind: str, name: str, layout: EntryLayout) -> str: - return f"{kind:<{layout.type_width}} {name:<{layout.name_width}} " - - - def emit_inline_entry(self, kind: str, name: str, pairs: list[str], layout: EntryLayout) -> str: - prefix = self.entry_prefix(kind, name, layout) - body = " ".join(pairs) - return f"{prefix}{{ {body} }}" - - - def value_requires_block(self, value: Any) -> bool: - if isinstance(value, dict): - return True - if isinstance(value, list): - return True - if isinstance(value, str) and self.looks_like_series_text(value): - return True - return False - - - def emit_braced_value(self, label: str, value: Any, indent: str = "") -> list[str]: - statement = self.normalized_statement_name(label) - if isinstance(value, str): - return [f"{indent}{statement} {{ {value.strip()} }}"] - if self.is_scalar_sequence(value): - body = " ".join(self.format_freeform_cell(item) for item in value) - return [f"{indent}{statement} {{ {body} }}"] - if self.is_matrix_like(value): - lines = [f"{indent}{statement} {{"] - child_indent = indent + " " - for row in value: - lines.append(f"{child_indent}{self.format_row(row)}") - lines.append(f"{indent}}}") - return lines - return [f"{indent}{statement} {{ {self.format_row(value)} }}"] - - - def emit_parenthesized_block(self, statement: str, value: Any, indent: str = "") -> list[str]: - if isinstance(value, str): - return [f"{indent}{statement} {{ {value.strip()} }}"] - - entries: list[Any] - if isinstance(value, list): - entries = value - else: - raise ValueError(f"{statement} requires a string or entry list") - - lines = [f"{indent}{statement} {{"] - child_indent = indent + " " - for entry in entries: - lines.append(f"{child_indent}{self.format_parenthesized_entry(entry)}") - lines.append(f"{indent}}}") - return lines - - - def emit_generic_block(self, statement: str, payload: Any, indent: str = "") -> list[str]: - if isinstance(payload, list): - if not payload: - return [f"{indent}{statement} {{}}"] - if all(isinstance(item, dict) for item in payload): - lines: list[str] = [] - for item in payload: - lines.extend(self.emit_generic_block(statement, item, indent)) - return lines - return self.emit_braced_value(statement, payload, indent) - - if not isinstance(payload, dict): - return self.emit_braced_value(statement, payload, indent) - - lines = [f"{indent}{statement} {{"] - child_indent = indent + " " - for key, value in payload.items(): - if key == "entries": - continue - if isinstance(value, dict): - nested_statement = self.normalized_statement_name(key) - lines.extend(self.emit_generic_block(nested_statement, value, child_indent)) - continue - if isinstance(value, list) and value and all(isinstance(item, dict) for item in value): - nested_statement = self.normalized_statement_name(key) - for item in value: - lines.extend(self.emit_generic_block(nested_statement, item, child_indent)) - continue - if isinstance(value, list): - lines.extend(self.emit_braced_value(key, value, child_indent)) - continue - if isinstance(value, str): - if self.looks_like_series_text(value): - lines.extend(self.emit_braced_value(key, value, child_indent)) - else: - lines.append(self.emit_assignment(key, value, child_indent)) - continue - lines.append(self.emit_assignment(key, value, child_indent)) - lines.append(f"{indent}}}") - return lines - - - def emit_rho_block(self, field_name: str, payload: Any, indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError(f"{field_name} must be a scalar or object") - - if "silicon_width" in payload or "silicon_thickness" in payload: - lines = [f"{indent}RHO_VS_SI_WIDTH_AND_THICKNESS {{"] - child_indent = indent + " " - if "silicon_width" in payload: - lines.extend(self.emit_braced_value("WIDTH", payload["silicon_width"], child_indent)) - if "silicon_thickness" in payload: - lines.extend(self.emit_braced_value("THICKNESS", payload["silicon_thickness"], child_indent)) - if "values" in payload: - lines.extend(self.emit_braced_value("VALUES", payload["values"], child_indent)) - lines.append(f"{indent}}}") - return lines - - if "draw_width" in payload or "draw_spacing" in payload: - lines = [f"{indent}RHO_VS_WIDTH_AND_SPACING {{"] - child_indent = indent + " " - if "draw_spacing" in payload: - lines.extend(self.emit_braced_value("SPACINGS", payload["draw_spacing"], child_indent)) - if "draw_width" in payload: - lines.extend(self.emit_braced_value("WIDTHS", payload["draw_width"], child_indent)) - if "values" in payload: - lines.extend(self.emit_braced_value("VALUES", payload["values"], child_indent)) - lines.append(f"{indent}}}") - return lines - - raise ValueError("unsupported rho object shape") - - - def emit_wire_edge_enlargement(self, field_name: str, payload: Any, indent: str = "") -> list[str]: - qualifier = "" - if field_name == "wire_edge_enlargement_c": - qualifier = " CAPACITIVE_ONLY" - elif field_name == "wire_edge_enlargement_r": - qualifier = " RESISTIVE_ONLY" - - def emit_one(table: dict[str, Any]) -> list[str]: - lines = [f"{indent}ETCH_VS_WIDTH_AND_SPACING{qualifier} {{"] - child_indent = indent + " " - if "wee_spacings" in table: - lines.extend(self.emit_braced_value("SPACINGS", table["wee_spacings"], child_indent)) - if "wee_widths" in table: - lines.extend(self.emit_braced_value("WIDTHS", table["wee_widths"], child_indent)) - if "wee_adjustments" in table: - lines.extend(self.emit_braced_value("VALUES", table["wee_adjustments"], child_indent)) - lines.append(f"{indent}}}") - return lines - - if isinstance(payload, list): - lines: list[str] = [] - for item in payload: - if not isinstance(item, dict): - raise ValueError(f"{field_name} repeated payload must be a dict list") - lines.extend(emit_one(item)) - return lines - - if not isinstance(payload, dict): - raise ValueError(f"{field_name} must be a dict or dict list") - return emit_one(payload) - - - def emit_width_dependent_tc(self, payload: Any, indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError("width_dependent_tc must be an object") - widths = payload.get("widths", []) - tcr1 = payload.get("TCR1", []) - tcr2 = payload.get("TCR2", []) - if not isinstance(widths, list) or not isinstance(tcr1, list) or not isinstance(tcr2, list): - raise ValueError("width_dependent_tc widths/TCR1/TCR2 must be lists") - if len(widths) != len(tcr1) or len(widths) != len(tcr2): - raise ValueError("width_dependent_tc list lengths do not match") - - lines = [f"{indent}CRT_VS_SI_WIDTH {{"] - child_indent = indent + " " - for width, coeff1, coeff2 in zip(widths, tcr1, tcr2): - lines.append( - f"{child_indent}({self.format_freeform_cell(width)}, {self.format_freeform_cell(coeff1)}, {self.format_freeform_cell(coeff2)})" - ) - lines.append(f"{indent}}}") - return lines - - - def emit_wire_thickness_ratio(self, payload: Any, indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError("wire_thickness_ratio must be an object") - densities = payload.get("densities", []) - deltas = payload.get("thickness_deltas", []) - if not isinstance(densities, list) or not isinstance(deltas, list): - raise ValueError("wire_thickness_ratio densities/thickness_deltas must be lists") - if len(densities) != len(deltas): - raise ValueError("wire_thickness_ratio list lengths do not match") - - qualifiers = payload.get("qualifiers") - qualifier_text = "" - if isinstance(qualifiers, list) and qualifiers: - qualifier_text = " " + " ".join(str(item).upper() for item in qualifiers) - - lines = [f"{indent}THICKNESS_VS_DENSITY{qualifier_text} {{"] - child_indent = indent + " " - for density, delta in zip(densities, deltas): - lines.append(f"{child_indent}({self.format_freeform_cell(density)}, {self.format_freeform_cell(delta)})") - lines.append(f"{indent}}}") - return lines - - - def normalize_polynomial_tables(payload: dict[str, Any]) -> list[list[Any]]: - coefficients = payload.get("polynomial_coefficients") - if not isinstance(coefficients, list): - raise ValueError("polynomial_coefficients must be a list") - if not coefficients: - return [] - - if all(isinstance(row, list) and all(not isinstance(cell, list) for cell in row) for row in coefficients): - return [coefficients] - - if all(isinstance(table, list) for table in coefficients): - return coefficients - - raise ValueError("unsupported polynomial_coefficients payload") - - - def emit_polynomial_based_thickness_variation(self, payload: Any, indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError("polynomial_based_thickness_variation must be an object") - lines = [f"{indent}POLYNOMIAL_BASED_THICKNESS_VARIATION {{"] - child_indent = indent + " " - if "density_polynomial_orders" in payload: - lines.extend( - self.emit_braced_value("DENSITY_POLYNOMIAL_ORDERS", payload["density_polynomial_orders"], child_indent) - ) - if "width_polynomial_orders" in payload: - lines.extend( - self.emit_braced_value("WIDTH_POLYNOMIAL_ORDERS", payload["width_polynomial_orders"], child_indent) - ) - if "width_ranges" in payload: - lines.extend(self.emit_braced_value("WIDTH_RANGES", payload["width_ranges"], child_indent)) - - for table in self.normalize_polynomial_tables(payload): - lines.append(f"{child_indent}POLYNOMIAL_COEFFICIENTS {{") - row_indent = child_indent + " " - for row in table: - lines.append(f"{row_indent}{self.format_row(row)}") - lines.append(f"{child_indent}}}") - - lines.append(f"{indent}}}") - return lines - - - def emit_ild_vs_width_and_spacing(self, payload: Any, indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError("ild_vs_width_and_spacing must be an object") - lines = [f"{indent}ILD_VS_WIDTH_AND_SPACING {{"] - child_indent = indent + " " - if "dielectric_layer" in payload: - lines.append(self.emit_assignment("dielectric_layer", payload["dielectric_layer"], child_indent)) - if "widths" in payload: - lines.extend(self.emit_braced_value("WIDTHS", payload["widths"], child_indent)) - if "spacings" in payload: - lines.extend(self.emit_braced_value("SPACINGS", payload["spacings"], child_indent)) - if "thickness_changes" in payload: - lines.extend(self.emit_braced_value("THICKNESS_CHANGES", payload["thickness_changes"], child_indent)) - lines.append(f"{indent}}}") - return lines - - - def infer_qualifier(self, entry: dict[str, Any]) -> str: - has_c = "etch_shrink_c" in entry - has_r = "etch_shrink_r" in entry - has_rc = "etch_shrink" in entry - if has_c and not has_r and not has_rc: - return " CAPACITIVE_ONLY" - if has_r and not has_c and not has_rc: - return " RESISTIVE_ONLY" - return "" - - - def emit_etch_vs_width_and_length(self, payload: Any, entry: dict[str, Any], indent: str = "") -> list[str]: - if not isinstance(payload, dict): - raise ValueError("etch_vs_width_and_length must be an object") - qualifier = self.infer_qualifier(entry) - lines = [f"{indent}ETCH_VS_WIDTH_AND_LENGTH{qualifier} {{"] - child_indent = indent + " " - if "lengths" in payload: - lines.extend(self.emit_braced_value("LENGTHS", payload["lengths"], child_indent)) - if "widths" in payload: - lines.extend(self.emit_braced_value("WIDTHS", payload["widths"], child_indent)) - if "entries" in payload: - lines.extend(self.emit_parenthesized_block("VALUES", payload["entries"], child_indent)) - elif "values" in payload: - lines.extend(self.emit_braced_value("VALUES", payload["values"], child_indent)) - lines.append(f"{indent}}}") - return lines - - - def emit_etch_vs_contact_and_gate_spacings(self, payload: Any, entry: dict[str, Any], indent: str = "") -> list[str]: - statement = "ETCH_VS_CONTACT_AND_GATE_SPACINGS" + self.infer_qualifier(entry) - return self.emit_generic_block(statement, payload, indent) - - - def emit_special_conductor_block(self, field_name: str, payload: Any, entry: dict[str, Any], indent: str = "") -> list[str]: - if field_name == "rho": - return self.emit_rho_block(field_name, payload, indent) - if field_name in {"wire_edge_enlargement", "wire_edge_enlargement_c", "wire_edge_enlargement_r"}: - return self.emit_wire_edge_enlargement(field_name, payload, indent) - if field_name == "width_dependent_tc": - return self.emit_width_dependent_tc(payload, indent) - if field_name == "wire_thickness_ratio": - return self.emit_wire_thickness_ratio(payload, indent) - if field_name == "polynomial_based_thickness_variation": - return self.emit_polynomial_based_thickness_variation(payload, indent) - if field_name == "gate_to_diffusion_cap": - return self.emit_generic_block("GATE_TO_DIFFUSION_CAP", payload, indent) - if field_name == "ild_vs_width_and_spacing": - return self.emit_ild_vs_width_and_spacing(payload, indent) - raise ValueError(f"unsupported conductor block field: {field_name}") - - - def emit_special_via_block(self, field_name: str, payload: Any, entry: dict[str, Any], indent: str = "") -> list[str]: - if field_name == "contact_resistance": - return self.emit_parenthesized_block("RPV_VS_AREA", payload, indent) - if field_name == "crt_vs_area": - return self.emit_parenthesized_block("CRT_VS_AREA", payload, indent) - if field_name == "etch_vs_width_and_length": - return self.emit_etch_vs_width_and_length(payload, entry, indent) - if field_name == "etch_vs_contact_and_gate_spacings": - return self.emit_etch_vs_contact_and_gate_spacings(payload, entry, indent) - raise ValueError(f"unsupported via block field: {field_name}") - - - def conductor_thickness(self, entry: dict[str, Any]) -> Decimal: - return self.as_decimal(entry.get("top_height")) - self.as_decimal(entry.get("btm_height")) - - - def dielectric_thickness(self, entry: dict[str, Any], layers_by_name: dict[str, dict[str, Any]]) -> Decimal: - del layers_by_name - return self.as_decimal(entry.get("top_height")) - self.as_decimal(entry.get("btm_height")) - - - def emit_conductor(self, entry: dict[str, Any], layout: EntryLayout) -> str: - name = self.format_scalar(entry["name"]) - lines = [f"{self.entry_prefix('CONDUCTOR', name, layout)}{{"] - indent = " " - consumed = set(self.RESERVED_KEYS) - inline_pairs: list[str] = [] - has_nested_blocks = False - - inline_pairs.append(self.format_assignment_pair("THICKNESS", self.conductor_thickness(entry))) - - scalar_order = [ - "layer_type", - "min_width", - "min_spacing", - "gate_to_contact_smin", - "side_tangent", - "rho", - "rpsq", - "TCR1", - "TCR2", - "temp_reference", - "etch_shrink", - "etch_shrink_c", - "etch_shrink_r", - "damage_thickness", - "damage_er", - ] - - for key in scalar_order: - if key not in entry: - continue - value = entry[key] - if key == "rho" and isinstance(value, dict): - continue - if key == "rpsq" and isinstance(value, dict): - raise ValueError("rpsq table/object forms are not supported in json->itf") - if key in self.SCALAR_ETCH_FIELDS: - inline_pairs.append(self.format_assignment_pair(self.SCALAR_ETCH_FIELDS[key], -self.as_decimal(value))) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - consumed.add(key) - - block_order = [ - "polynomial_based_thickness_variation", - "rho", - "wire_edge_enlargement", - "wire_edge_enlargement_c", - "wire_edge_enlargement_r", - "ild_vs_width_and_spacing", - "width_dependent_tc", - "wire_thickness_ratio", - "gate_to_diffusion_cap", - ] - - for key in block_order: - if key not in entry: - continue - has_nested_blocks = True - lines.extend(self.emit_special_conductor_block(key, entry[key], entry, indent)) - consumed.add(key) - - for key, value in entry.items(): - if key in consumed: - continue - if isinstance(value, dict) or (isinstance(value, list) and value and all(isinstance(item, dict) for item in value)): - has_nested_blocks = True - lines.extend(self.emit_generic_block(self.normalized_statement_name(key), value, indent)) - elif isinstance(value, list): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - elif isinstance(value, str): - if self.looks_like_series_text(value): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - - if not has_nested_blocks: - return self.emit_inline_entry("CONDUCTOR", name, inline_pairs, layout) - - if inline_pairs: - for pair in reversed(inline_pairs): - key, value = pair.split(" = ", 1) - lines.insert(1, f"{indent}{key} = {value}") - lines.append("}") - return "\n".join(lines) - - - def emit_dielectric( - self, - entry: dict[str, Any], - layers_by_name: dict[str, dict[str, Any]], - layout: EntryLayout, - ) -> str: - name = self.format_scalar(entry["name"]) - lines = [f"{self.entry_prefix('DIELECTRIC', name, layout)}{{"] - indent = " " - consumed = set(self.RESERVED_KEYS) - inline_pairs: list[str] = [] - has_nested_blocks = False - - inline_pairs.append(self.format_assignment_pair("THICKNESS", self.dielectric_thickness(entry, layers_by_name))) - consumed.add("measured_from") - - scalar_order = [ - "epsilon_ratio", - "damage_thickness", - "damage_er", - "top_thickness", - "side_thickness", - "measured_from", - "temp_reference", - ] - for key in scalar_order: - if key not in entry: - continue - inline_pairs.append(self.format_assignment_pair(key, entry[key])) - consumed.add(key) - - for key, value in entry.items(): - if key in consumed: - continue - if isinstance(value, dict) or (isinstance(value, list) and value and all(isinstance(item, dict) for item in value)): - has_nested_blocks = True - lines.extend(self.emit_generic_block(self.normalized_statement_name(key), value, indent)) - elif isinstance(value, list): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - elif isinstance(value, str): - if self.looks_like_series_text(value): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - - if not has_nested_blocks: - return self.emit_inline_entry("DIELECTRIC", name, inline_pairs, layout) - - if inline_pairs: - for pair in reversed(inline_pairs): - key, value = pair.split(" = ", 1) - lines.insert(1, f"{indent}{key} = {value}") - lines.append("}") - return "\n".join(lines) - - - def emit_via(self, entry: dict[str, Any], layout: EntryLayout) -> str: - name = self.format_scalar(entry["name"]) - lines = [f"{self.entry_prefix('VIA', name, layout)}{{"] - indent = " " - consumed = set(self.RESERVED_KEYS) - inline_pairs: list[str] = [] - has_nested_blocks = False - - scalar_order = [ - "btm_layer", - "top_layer", - "area", - "res_per_via", - "rho", - "TCR1", - "TCR2", - "temp_reference", - "etch_shrink", - "etch_shrink_c", - "etch_shrink_r", - ] - for key in scalar_order: - if key not in entry: - continue - if key in self.SCALAR_ETCH_FIELDS: - inline_pairs.append(self.format_assignment_pair(self.SCALAR_ETCH_FIELDS[key], -self.as_decimal(entry[key]))) - else: - inline_pairs.append(self.format_assignment_pair(key, entry[key])) - consumed.add(key) - - block_order = [ - "contact_resistance", - "crt_vs_area", - "etch_vs_width_and_length", - "etch_vs_contact_and_gate_spacings", - ] - for key in block_order: - if key not in entry: - continue - has_nested_blocks = True - lines.extend(self.emit_special_via_block(key, entry[key], entry, indent)) - consumed.add(key) - - for key, value in entry.items(): - if key in consumed: - continue - if isinstance(value, dict) or (isinstance(value, list) and value and all(isinstance(item, dict) for item in value)): - has_nested_blocks = True - lines.extend(self.emit_generic_block(self.normalized_statement_name(key), value, indent)) - elif isinstance(value, list): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - elif isinstance(value, str): - if self.looks_like_series_text(value): - has_nested_blocks = True - lines.extend(self.emit_braced_value(key, value, indent)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - else: - inline_pairs.append(self.format_assignment_pair(key, value)) - - if not has_nested_blocks: - return self.emit_inline_entry("VIA", name, inline_pairs, layout) - - if inline_pairs: - for pair in reversed(inline_pairs): - key, value = pair.split(" = ", 1) - lines.insert(1, f"{indent}{key} = {value}") - lines.append("}") - return "\n".join(lines) - - - def emit_process(self, process: dict[str, Any]) -> list[str]: - lines: list[str] = [] - consumed: set[str] = set() - for key in ("name", "temperature", "half_node_scale_factor"): - if key in process: - lines.append(self.emit_assignment(self.PROCESS_FIELD_MAP[key], process[key])) - consumed.add(key) - for key, value in process.items(): - if key in consumed: - continue - lines.append(self.emit_assignment(key, value)) - return lines - - - def build_entry_layout(self, entries: list[dict[str, Any]], kinds: list[str]) -> EntryLayout: - type_width = max(len(kind) for kind in kinds) - names = [self.format_scalar(entry["name"]) for entry in entries if "name" in entry] - name_width = max([len(name) for name in names] + [1]) - return self.EntryLayout(type_width=type_width, name_width=name_width) - - - def sort_layers(self, conductors: list[dict[str, Any]], dielectrics: list[dict[str, Any]]) -> list[tuple[str, dict[str, Any]]]: - ordered: list[tuple[Decimal, int, int, str, dict[str, Any]]] = [] - for index, entry in enumerate(dielectrics): - ordered.append((self.as_decimal(entry.get("btm_height")), 0, index, "DIELECTRIC", entry)) - for index, entry in enumerate(conductors): - ordered.append((self.as_decimal(entry.get("btm_height")), 1, index, "CONDUCTOR", entry)) - ordered.sort(key=lambda item: (-item[0], item[1], item[2])) - return [(kind, entry) for _, _, _, kind, entry in ordered] - - - def json_to_itf(self, data: dict[str, Any]) -> str: - if not isinstance(data, dict): - raise ValueError("top-level JSON must be an object") - - process = data.get("process", {}) - conductors = data.get("conductors", []) - dielectrics = data.get("dielectrics", []) - vias = data.get("vias", []) - - if not isinstance(process, dict): - raise ValueError("process must be an object") - if not isinstance(conductors, list) or not all(isinstance(item, dict) for item in conductors): - raise ValueError("conductors must be an object list") - if not isinstance(dielectrics, list) or not all(isinstance(item, dict) for item in dielectrics): - raise ValueError("dielectrics must be an object list") - if not isinstance(vias, list) or not all(isinstance(item, dict) for item in vias): - raise ValueError("vias must be an object list") - - layers_by_name = {entry["name"]: entry for entry in conductors + dielectrics if "name" in entry} - layer_layout = self.build_entry_layout(conductors + dielectrics, ["CONDUCTOR", "DIELECTRIC"]) - via_layout = self.build_entry_layout(vias, ["VIA"]) - - lines: list[str] = [] - process_lines = self.emit_process(process) - lines.extend(process_lines) - - rendered_blocks: list[tuple[str, bool]] = [] - - for kind, entry in self.sort_layers(conductors, dielectrics): - if kind == "DIELECTRIC": - block = self.emit_dielectric(entry, layers_by_name, layer_layout) - else: - block = self.emit_conductor(entry, layer_layout) - rendered_blocks.append((block, "\n" in block)) - - for entry in vias: - block = self.emit_via(entry, via_layout) - rendered_blocks.append((block, "\n" in block)) - - if process_lines and rendered_blocks: - lines.append("") - - previous_multiline = False - for index, (block, is_multiline) in enumerate(rendered_blocks): - if index > 0 and (is_multiline or previous_multiline): - lines.append("") - lines.extend(block.splitlines()) - previous_multiline = is_multiline - - return "\n".join(lines) + "\n" - - - def load_json(self, path: Path = None) -> dict[str, Any]: - if path is None: - path = Path(self.input_path) - return json.loads( - path.read_text(), - parse_int=self.parse_json_number, - parse_float=self.parse_json_number, - ) + ) \ No newline at end of file diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index d7579ae8..56dfd0c9 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -2,7 +2,6 @@ # -*- encoding: utf-8 -*- import sys import os -import re from chipcompiler.data import WorkspaceStep, Workspace, StateEnum, StepEnum from chipcompiler.tools.ecc.module import ECCToolsModule @@ -784,19 +783,6 @@ def run_rcx(workspace: Workspace, """ run rcx """ - def run_jsons_to_itf(ecc_module : ECCToolsModule) -> bool: - config=json_read(workspace.config.get(StepEnum.RCX.value, "")) - corners_dict = config.get("corners", []) - for item in corners_dict: - json_file = item.get("ecc_tf", "") - itf_file = item.get("itf_file", "") - - if not os.path.exists(json_file): - return False - - ecc_module.rcx_json_to_itf(json_path=json_file, itf_path=itf_file) - return True - result = False sub_flow = EccSubFlow(workspace=workspace, @@ -808,24 +794,19 @@ def run_jsons_to_itf(ecc_module : ECCToolsModule) -> bool: if ecc_module is not None: sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) - if not run_jsons_to_itf(ecc_module): - sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Imcomplete) - result = False - else: - rcx_config_path = workspace.config.get(StepEnum.RCX.value, "") - rcx_config = json_read(rcx_config_path) - rcx_pdk = str(rcx_config.get("pdk", "") or "").strip() - ecc_module.init_rcx(config=rcx_config_path, pdk=rcx_pdk) - ecc_module.run_rcx() - ecc_module.report_rcx() - sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Success) - - save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False, report_timing=False) - - sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, - state=StateEnum.Success) - result = True + ecc_module.init_rcx(config=workspace.config.get(StepEnum.RCX.value, ""), + pdk=workspace.pdk.name) + ecc_module.run_rcx() + ecc_module.report_rcx() + sub_flow.update_step(step_name=EccSubFlowEnum.run_rcx.value, state=StateEnum.Success) + + save_data(workspace=workspace, step=step, ecc_module=ecc_module, feature_step=False, report_timing=False) + + sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, + state=StateEnum.Success) + + result = True return result @@ -843,97 +824,34 @@ def safe_dir_name(name: str) -> str: ) return value or "spef" - def temperature_key(temperature) -> str: + def temperature_token(temperature) -> str: try: numeric = float(temperature) if numeric.is_integer(): - return str(int(numeric)) + temperature = int(numeric) except (TypeError, ValueError): pass - return str(temperature) - - def temperature_token(temperature) -> str: - return temperature_key(temperature).replace("-", "m").replace(".", "p") - - def resolve_config_path(path: str) -> str: - if path.startswith("/"): - relative = path[1:] - workspace_prefixes = ( - "CTS_ecc", "Floorplan_ecc", "fixFanout_ecc", "place_ecc", - "legalization_ecc", "route_ecc", "drc_ecc", "filler_ecc", - "RCX_ecc", "rcx_ecc", "sta_ecc", "harden_ecc", "config", - "origin", "home", - ) - pdk_prefixes = ("IP", "prtech", "corners") - prefix = relative.split("/", 1)[0] - if prefix in workspace_prefixes: - return os.path.join(workspace.directory, relative) - if prefix in pdk_prefixes: - return os.path.join(workspace.pdk.root, relative) - return path - - def normalize_spef_path(spef_file: str) -> str: - if not spef_file or os.path.exists(spef_file): - return spef_file - - dirname = os.path.dirname(spef_file) - tail = os.path.basename(spef_file) - parts = tail.rsplit("_M", 1) - if len(parts) == 2 and parts[1].endswith("C.spef"): - normalized = os.path.join(dirname, f"{parts[0]}_m{parts[1]}") - if os.path.exists(normalized): - return normalized - return spef_file - - def normalize_liberty_path(liberty_file: str) -> str: - if not liberty_file or os.path.exists(liberty_file): - return liberty_file - - liberty_dir = os.path.dirname(liberty_file) - cell_dir = os.path.basename(os.path.dirname(liberty_dir)) - tail = os.path.basename(liberty_file) - match = re.match(r"^(ics55_LLSC_H7C[0-9A-Za-z]+)(_.+)$", tail) - if match: - normalized = os.path.join(liberty_dir, f"{cell_dir}{match.group(2)}") - if os.path.exists(normalized): - return normalized - return liberty_file - - def find_liberty_corner(sta_data: dict, corner_name: str) -> dict | None: - for liberty in sta_data.get("liberty", []): - if liberty.get("corner") == corner_name: - return liberty - return None - - def find_rcx_corner(rcx_data: dict, rcx_corner_name: str) -> dict | None: - for corner in rcx_data.get("corners", []): - if corner.get("name") == rcx_corner_name: - return corner - return None - - def find_spef_for_temp(rcx_corner: dict, temperature) -> str: - spef_file = rcx_corner.get("spef_file", "") - if isinstance(spef_file, str): - return spef_file - - temp_key = temperature_key(temperature) - for spef_item in spef_file: - if not isinstance(spef_item, dict): - continue - for spef_temperature, spef_path in spef_item.items(): - if temperature_key(spef_temperature) == temp_key: - return spef_path - return "" + return str(temperature).replace("-", "m").replace(".", "p") def collect_signoff_items() -> list[dict]: sta_config = workspace.config.get(StepEnum.STA.value, "") sta_data = json_read(sta_config) rcx_data = json_read(workspace.config.get(StepEnum.RCX.value, "")) + rcx_output_dir = str(rcx_data.get("output", "") or "") + if rcx_output_dir.startswith("/"): + relative_output_dir = rcx_output_dir[1:] + if relative_output_dir.split("/", 1)[0] in ("RCX_ecc", "rcx_ecc"): + rcx_output_dir = os.path.join(workspace.directory, relative_output_dir) + + liberty_by_corner = { + liberty.get("corner"): liberty + for liberty in sta_data.get("liberty", []) + } items = [] for signoff_group in sta_data.get("signoff", []): for corner_name, rcx_corner_names in signoff_group.items(): - liberty = find_liberty_corner(sta_data, corner_name) + liberty = liberty_by_corner.get(corner_name) if liberty is None: workspace.logger.error("No liberty corner '%s' found in %s", corner_name, @@ -941,28 +859,19 @@ def collect_signoff_items() -> list[dict]: return [] temperature = liberty.get("temperature") - liberty_files = [ - normalize_liberty_path(resolve_config_path(path)) - for path in liberty.get("path", []) - ] + liberty_files = liberty.get("path", []) for rcx_corner_name in rcx_corner_names: - rcx_corner = find_rcx_corner(rcx_data, rcx_corner_name) - if rcx_corner is None: - workspace.logger.error("No RCX corner '%s' found in %s", - rcx_corner_name, - workspace.config.get(StepEnum.RCX.value, "")) - return [] - - spef_file = normalize_spef_path( - resolve_config_path(find_spef_for_temp(rcx_corner, temperature)) - ) items.append({ "corner": corner_name, "temperature": temperature, "rcx_corner": rcx_corner_name, "liberty_files": liberty_files, - "spef_file": spef_file, + "spef_file": os.path.join( + rcx_output_dir, + f"{workspace.design.name}_{rcx_corner_name}_" + f"{temperature_token(temperature)}C.spef", + ), }) return items @@ -976,94 +885,101 @@ def collect_signoff_items() -> list[dict]: step=step, ecc_module = ecc_module) - if ecc_module is not None: - sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) + if ecc_module is None: + return result - signoff_items = collect_signoff_items() - if len(signoff_items) <= 0: - workspace.logger.error("No signoff STA items found") + sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success) + + signoff_items = collect_signoff_items() + if not signoff_items: + workspace.logger.error("No signoff STA items found") + sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, + state=StateEnum.Imcomplete) + return False + + if not os.path.exists(workspace.pdk.sdc): + workspace.logger.error("STA SDC does not exist: %s", + workspace.pdk.sdc) + sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, + state=StateEnum.Imcomplete) + return False + + for signoff_item in signoff_items: + corner_name = signoff_item["corner"] + temperature = signoff_item["temperature"] + rcx_corner_name = signoff_item["rcx_corner"] + liberty_files = signoff_item["liberty_files"] + spef_file = signoff_item["spef_file"] + + if not os.path.exists(spef_file): + workspace.logger.error( + "STA SPEF does not exist for %s/%s at %sC: %s", + corner_name, + rcx_corner_name, + temperature, + spef_file, + ) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Imcomplete) return False - if not os.path.exists(workspace.pdk.sdc): - workspace.logger.error("STA SDC does not exist: %s", - workspace.pdk.sdc) + missing_liberty_files = [ + lib_path for lib_path in liberty_files + if not os.path.exists(lib_path) + ] + if len(liberty_files) <= 0 or missing_liberty_files: + workspace.logger.error( + "STA liberty does not exist for %s: %s; missing: %s", + corner_name, + liberty_files, + missing_liberty_files, + ) sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Imcomplete) return False - for signoff_item in signoff_items: - corner_name = signoff_item["corner"] - temperature = signoff_item["temperature"] - rcx_corner_name = signoff_item["rcx_corner"] - liberty_files = signoff_item["liberty_files"] - spef_file = signoff_item["spef_file"] - - if not os.path.exists(spef_file): - workspace.logger.error( - "STA SPEF does not exist for %s/%s at %sC: %s", - corner_name, - rcx_corner_name, - temperature, - spef_file, - ) - sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, - state=StateEnum.Imcomplete) - return False - - if len(liberty_files) <= 0 or any(not os.path.exists(lib_path) for lib_path in liberty_files): - workspace.logger.error( - "STA liberty does not exist for %s: %s", - corner_name, - liberty_files, - ) - sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, - state=StateEnum.Imcomplete) - return False + report_corner_dir = f"{corner_name}_{temperature_token(temperature)}" + report_dir = os.path.join( + step.output.get("dir", ""), + safe_dir_name(report_corner_dir), + safe_dir_name(rcx_corner_name), + ) + os.makedirs(report_dir, exist_ok=True) - report_corner_dir = f"{corner_name}_{temperature_token(temperature)}" - report_dir = os.path.join( - step.output.get("dir", ""), - safe_dir_name(report_corner_dir), - safe_dir_name(rcx_corner_name), + try: + ecc_module.update_sta_data_config( + db_config=workspace.config.get("db", ""), + output_dir=step.output.get("dir", ""), + lib_paths=liberty_files, + sdc_path=workspace.pdk.sdc, ) - os.makedirs(report_dir, exist_ok=True) - - try: - ecc_module.update_sta_data_config( - db_config=workspace.config.get("db", ""), - output_dir=step.output.get("dir", ""), - lib_paths=liberty_files, - sdc_path=workspace.pdk.sdc, - ) - ecc_module.release_sta() - ecc_module.init_sta(output_dir=report_dir, - top_module=workspace.design.top_module, - lib_paths=liberty_files, - sdc_path=workspace.pdk.sdc) - ecc_module.read_spef(file_name=spef_file) - ecc_module.report_timing() - finally: - ecc_module.release_sta() + ecc_module.release_sta() + ecc_module.init_sta(output_dir=report_dir, + top_module=workspace.design.top_module, + lib_paths=liberty_files, + sdc_path=workspace.pdk.sdc) + ecc_module.read_spef(file_name=spef_file) + ecc_module.report_timing() + finally: + ecc_module.release_sta() - workspace.logger.info( - "STA report for %s/%s at %sC saved to %s", - corner_name, - rcx_corner_name, - temperature, - report_dir, - ) + workspace.logger.info( + "STA report for %s/%s at %sC saved to %s", + corner_name, + rcx_corner_name, + temperature, + report_dir, + ) - sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Success) - - result = save_data(workspace=workspace, - step=step, - ecc_module=ecc_module, - feature_step=False, - report_timing=False) - - sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, - state=StateEnum.Success) + sub_flow.update_step(step_name=EccSubFlowEnum.run_sta.value, state=StateEnum.Success) + + result = save_data(workspace=workspace, + step=step, + ecc_module=ecc_module, + feature_step=False, + report_timing=False) + + sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, + state=StateEnum.Success) return result From ae7d0807b2a56e554fd42849c8f6313d335ac81c Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 9 Jun 2026 21:27:42 +0800 Subject: [PATCH 12/14] change ecc_py path for ci --- chipcompiler/tools/ecc/module.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index a1a36457..347d0fff 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -17,8 +17,7 @@ class ECCToolsModule: """ def __init__(self): try: - # from ecc_tools_bin import ecc_py as ecc - from chipcompiler.tools.ecc.bin import ecc_py as ecc + from ecc_tools_bin import ecc_py as ecc except ImportError: try: from chipcompiler.tools.ecc.bin import ecc_py as ecc From 337d973deaed364fd9960c9d85887f867dc62198 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 9 Jun 2026 21:41:30 +0800 Subject: [PATCH 13/14] delete test soc --- test/test_soc.py | 73 ------------------------------------------------ 1 file changed, 73 deletions(-) delete mode 100644 test/test_soc.py diff --git a/test/test_soc.py b/test/test_soc.py deleted file mode 100644 index 26c1bb74..00000000 --- a/test/test_soc.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -import sys -import os - -current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] -sys.path.append(root) - -from chipcompiler.data import ( - create_workspace, - load_workspace, - log_workspace, - log_parameters, - StepEnum, - StateEnum, - get_pdk, - get_design_parameters -) - -from chipcompiler.engine import ( - EngineDB, - EngineFlow -) - -def test_ics55_soc(): - workspace_dir="{}/test/examples/soc".format(root) - - workspace = load_workspace(workspace_dir) - - from chipcompiler.engine import EngineDB - # build engine_db for workspace - engine_db = EngineDB(workspace=workspace) - # build engine flow for workspace - engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) - if not engine_flow.has_init(): - from chipcompiler.rtl2gds import build_rtl2gds_flow - steps = build_rtl2gds_flow() - for step, tool, state in steps: - engine_flow.add_step(step=step, tool=tool, state=state) - - engine_flow.create_step_workspaces() - - engine_flow.run_steps() - -def test_ics55_core(): - workspace_dir="{}/test/examples/core".format(root) - - workspace = load_workspace(workspace_dir) - - from chipcompiler.engine import EngineDB - # build engine_db for workspace - engine_db = EngineDB(workspace=workspace) - # build engine flow for workspace - engine_flow = EngineFlow(workspace=workspace, engine_db=engine_db) - if not engine_flow.has_init(): - from chipcompiler.rtl2gds import build_harden_flow - steps = build_harden_flow() - for step, tool, state in steps: - engine_flow.add_step(step=step, tool=tool, state=state) - - engine_flow.create_step_workspaces() - - engine_flow.run_steps() - - -if __name__ == "__main__": - test_ics55_soc() - - test_ics55_core() - - exit(0) \ No newline at end of file From db116ae3778ea5f1e6376d20c5fd6b1cbea13934 Mon Sep 17 00:00:00 2001 From: Yell-walkalone <12112088@qq.com> Date: Tue, 9 Jun 2026 23:28:37 +0800 Subject: [PATCH 14/14] fix bugs for ci --- .github/workflows/ci.yml | 2 +- test/test_ecc_tools_module.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64630ac0..44499f8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: # run: uv run pyright chipcompiler - name: Pytest - run: uv run pytest test/ --ignore=test/test_harden.py --ignore=test/test_rcx.py --cov=chipcompiler --cov-report= + run: uv run pytest test/ --ignore=test/test_harden.py --ignore=test/test_rcx.py --ignore=test/examples/test_soc.py --cov=chipcompiler --cov-report= - name: Publish coverage summary if: always() diff --git a/test/test_ecc_tools_module.py b/test/test_ecc_tools_module.py index 7748a784..52387646 100644 --- a/test/test_ecc_tools_module.py +++ b/test/test_ecc_tools_module.py @@ -27,10 +27,19 @@ def test_init_rcx_passes_pdk_when_configured(): assert module.ecc.calls == [{"config": "/tmp/rcx.json", "pdk": "ics55"}] -def test_init_rcx_omits_empty_pdk_for_backward_compatibility(): +def test_init_rcx_defaults_to_ics55_pdk(): module = ECCToolsModule.__new__(ECCToolsModule) module.ecc = FakeEcc() assert module.init_rcx(config="/tmp/rcx.json") is True + assert module.ecc.calls == [{"config": "/tmp/rcx.json", "pdk": "ics55"}] + + +def test_init_rcx_omits_explicit_empty_pdk_for_backward_compatibility(): + module = ECCToolsModule.__new__(ECCToolsModule) + module.ecc = FakeEcc() + + assert module.init_rcx(config="/tmp/rcx.json", pdk="") is True + assert module.ecc.calls == [{"config": "/tmp/rcx.json"}]