diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f17485a4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + pull_request: + +jobs: + format: + name: Formatter Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v3 + with: + version: latest + + - name: Sync dependencies + run: uv sync --frozen --all-groups --python 3.10 + + - name: Ruff format check + run: uv run ruff format --check chipcompiler test benchmark diff --git a/benchmark/__init__.py b/benchmark/__init__.py index b559b5e2..64e30c86 100644 --- a/benchmark/__init__.py +++ b/benchmark/__init__.py @@ -1,17 +1,4 @@ -from .benchmark import ( - run_benchmark, - benchmark_statis, - benchmark_metrics -) +from .benchmark import benchmark_metrics, benchmark_statis, run_benchmark +from .get_parameters import get_parameters -from .get_parameters import ( - get_parameters -) - - -__all__ = [ - 'run_benchmark', - 'benchmark_statis', - 'benchmark_metrics', - 'get_parameters' -] \ No newline at end of file +__all__ = ["run_benchmark", "benchmark_statis", "benchmark_metrics", "get_parameters"] diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 876e261d..333fee42 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -1,66 +1,57 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys import os +import sys current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] +root = current_dir.rsplit("/", 1)[0] sys.path.append(root) -from chipcompiler.data import ( +from chipcompiler.data import ( # noqa: E402 + StateEnum, + StepEnum, create_workspace, + get_pdk, log_workspace, - StepEnum, - StateEnum, - load_parameter, - get_pdk -) - -from chipcompiler.engine import ( - EngineDB, - EngineFlow ) +from chipcompiler.engine import EngineFlow # noqa: E402 +from chipcompiler.tools import build_step_metrics # noqa: E402 +from chipcompiler.utility import csv_write, json_read # noqa: E402 -from chipcompiler.tools import build_step_metrics - -from chipcompiler.utility import json_read, csv_write +from .get_parameters import get_parameters # noqa: E402 -from .get_parameters import get_parameters def has_value(value): - return value is not None and value!="" + return value is not None and value != "" -def get_tasks(benchmark_json : str, - target_dir: str = "", - batch_name: str = "", - design:str = ""): + +def get_tasks(benchmark_json: str, target_dir: str = "", batch_name: str = "", design: str = ""): from chipcompiler.utility import json_read - + benchmarks = json_read(benchmark_json) - + value_is_ok = True - + value_is_ok = value_is_ok & has_value(benchmarks.get("target_dir", "")) value_is_ok = value_is_ok & has_value(benchmarks.get("batch_name", "")) value_is_ok = value_is_ok & has_value(benchmarks.get("pdk", "")) - + if not value_is_ok: raise ValueError("Invalid benchmark json file, missing target_dir or batch_name or pdk") - + target_dir = target_dir if target_dir != "" else benchmarks.get("target_dir", "") batch_name = batch_name if batch_name != "" else benchmarks.get("batch_name", "") task_dir = f"{target_dir}/{batch_name}" os.makedirs(task_dir, exist_ok=True) - + designs = benchmarks.get("designs", []) - + # Create a list to hold all design tasks design_tasks = [] for design_info in designs: if design != "" and design != design_info.get("Design", ""): continue - + value_is_ok = True value_is_ok = value_is_ok & has_value(design_info.get("id", "")) # value_is_ok = value_is_ok & has_value(design_info.get("filelist", "")) @@ -76,26 +67,35 @@ def get_tasks(benchmark_json : str, value_is_ok = value_is_ok & has_value(design_info.get("Clock", "")) value_is_ok = value_is_ok & has_value(design_info.get("Frequency max [MHz]", "")) if not value_is_ok: - raise ValueError(f"Invalid design info for {design_info.get('id', '')}, missing required fields") - + raise ValueError( + f"Invalid design info for {design_info.get('id', '')}, missing required fields" + ) + # Collect task parameters - design_tasks.append((f"{task_dir}/{design_info.get('id', '')}", - benchmarks.get("pdk", ""), - design_info,)) - + design_tasks.append( + ( + f"{task_dir}/{design_info.get('id', '')}", + benchmarks.get("pdk", ""), + design_info, + ) + ) + return design_tasks -def run_benchmark(benchmark_json : str, - target_dir: str = "", - batch_name: str = "", - design:str = "", - max_processes = 10): +def run_benchmark( + benchmark_json: str, + target_dir: str = "", + batch_name: str = "", + design: str = "", + max_processes=10, +): design_tasks = get_tasks(benchmark_json, target_dir, batch_name, design) - + # Run tasks with manual process management (max 10 concurrent processes) running_processes = [] import multiprocessing + for task_args in design_tasks: if len(running_processes) >= max_processes: # Check for completed processes @@ -106,31 +106,31 @@ def run_benchmark(benchmark_json : str, else: # No completed processes, wait briefly import time + time.sleep(5) continue - + # Create a new non-daemon process p = multiprocessing.Process(target=run_single_design, args=task_args) p.daemon = False # Ensure process is not daemon so it can create children p.start() running_processes.append(p) - + # Wait for all remaining processes to complete for p in running_processes: p.join() - -def run_single_design(workspace_dir : str, - pdk_name : str, - design_info : dict): + + +def run_single_design(workspace_dir: str, pdk_name: str, design_info: dict): os.makedirs(workspace_dir, exist_ok=True) - + parameters = get_parameters(pdk_name=pdk_name) - + parameters.data["Design"] = design_info.get("Design", "") parameters.data["Top module"] = design_info.get("Top module", "") parameters.data["Clock"] = design_info.get("Clock", "") parameters.data["Frequency max [MHz]"] = design_info.get("Frequency max [MHz]", 100) - + pdk = get_pdk(pdk_name=pdk_name) input_rtl = design_info.get("rtl", "") @@ -154,7 +154,7 @@ def run_single_design(workspace_dir : str, input_filelist = "" else: raise ValueError(f"No valid input file found for design {design_info.get('Design', '')}") - + steps.append((StepEnum.FLOORPLAN, "ecc", StateEnum.Unstart)) steps.append((StepEnum.NETLIST_OPT, "ecc", StateEnum.Unstart)) steps.append((StepEnum.PLACEMENT, "ecc", StateEnum.Unstart)) @@ -163,7 +163,7 @@ def run_single_design(workspace_dir : str, steps.append((StepEnum.ROUTING, "ecc", StateEnum.Unstart)) steps.append((StepEnum.DRC, "ecc", StateEnum.Unstart)) steps.append((StepEnum.FILLER, "ecc", StateEnum.Unstart)) - + # create workspace workspace = create_workspace( directory=workspace_dir, @@ -171,24 +171,25 @@ def run_single_design(workspace_dir : str, origin_verilog=input_netlist, pdk=pdk, parameters=parameters, - input_filelist=input_filelist + input_filelist=input_filelist, ) - + engine_flow = EngineFlow(workspace=workspace) if not engine_flow.has_init(): for step, tool, state in steps: engine_flow.add_step(step=step, tool=tool, state=state) if engine_flow.is_flow_success(): - return + return engine_flow.create_step_workspaces() - + log_workspace(workspace=workspace) - + engine_flow.run_steps() -def benchmark_statis(benchmark_dir : str): + +def benchmark_statis(benchmark_dir: str): statis_csv = f"{benchmark_dir}/benchmark.statis.csv" - + header = [ "Workspace", "Design", @@ -202,61 +203,58 @@ def benchmark_statis(benchmark_dir : str): f"{StepEnum.DRC.value}", f"{StepEnum.FILLER.value}", ] - + total = 0 success_num = 0 results = [] - for root, dirs, files in os.walk(benchmark_dir): + for root, dirs, _files in os.walk(benchmark_dir): if root != benchmark_dir: break - + for dir in dirs: workspace_result = [] - + # workspace name workspace_result.append(dir) - + workspace_dir = os.path.join(root, dir) - + # design name parameter_json = f"{workspace_dir}/parameters.json" parameter_dict = json_read(file_path=parameter_json) design_name = parameter_dict.get("Design", "") workspace_result.append(design_name) - + flow_json = f"{workspace_dir}/flow.json" flow_dict = json_read(file_path=flow_json) - + step_result = [] is_success = True for step in flow_dict.get("steps", {}): state = step.get("state", "") step_result.append(state) - if "Success" != state: + if state != "Success": is_success = False - - success_num = success_num + 1 if is_success else success_num - + + success_num = success_num + 1 if is_success else success_num + workspace_result.extend(step_result) total += 1 - + results.append(workspace_result) - + print(f"process {dir} - {design_name} : {is_success}") - - results.append([f"success", f"{success_num} / {total}"]) + + results.append(["success", f"{success_num} / {total}"]) print(f"benchmark success {success_num} / {total}") - - csv_write(file_path=statis_csv, - header=[header], - data=results) - -def benchmark_metrics(benchmark_json : str, - target_dir: str = "", - batch_name: str = ""): + + csv_write(file_path=statis_csv, header=[header], data=results) + + +def benchmark_metrics(benchmark_json: str, target_dir: str = "", batch_name: str = ""): statis_csv = f"{target_dir}/{batch_name}/benchmark.metrics.csv" - + design_tasks = get_tasks(benchmark_json, target_dir, batch_name) header = [] @@ -264,30 +262,30 @@ def benchmark_metrics(benchmark_json : str, header2 = [] init_header = False results = [] - + pdk = None - for workspace_dir, pdk_name, design_info in design_tasks: + for workspace_dir, pdk_name, design_info in design_tasks: workspace_result = [] - + # workspace name workspace_result.append(design_info.get("id", "")) workspace_result.append(design_info.get("Design", "")) - + if pdk is None: pdk = get_pdk(pdk_name=pdk_name) - + parameters = get_parameters(pdk_name=pdk_name) - + parameters.data["Design"] = design_info.get("Design", "") parameters.data["Top module"] = design_info.get("Top module", "") parameters.data["Clock"] = design_info.get("Clock", "") parameters.data["Frequency max [MHz]"] = design_info.get("Frequency max [MHz]", 100) - + input_rtl = design_info.get("rtl", "") input_verilog = design_info.get("netlist", "") input_filelist = design_info.get("filelist", "") - + input_netlist = "" if input_filelist and os.path.exists(input_filelist): # Use filelist for synthesis (input_netlist optional) @@ -301,16 +299,16 @@ def benchmark_metrics(benchmark_json : str, input_filelist = "" else: continue - + workspace = create_workspace( directory=workspace_dir, origin_def="", origin_verilog=input_netlist, pdk=pdk, parameters=parameters, - input_filelist=input_filelist + input_filelist=input_filelist, ) - + engine_flow = EngineFlow(workspace=workspace) if not engine_flow.has_init(): break @@ -323,42 +321,35 @@ def benchmark_metrics(benchmark_json : str, header2.append("design") header1.append("") header2.append("peak memory (mb)") - + step_result = [] peak_memory = 0 for workspace_step in engine_flow.workspace_steps: - step = engine_flow.get_step(name=workspace_step.name, - tool=workspace_step.tool) - if engine_flow.check_state(name=workspace_step.name, - tool=workspace_step.tool, - state=StateEnum.Success): - metrics = build_step_metrics(workspace=workspace, - step=workspace_step) + step = engine_flow.get_step(name=workspace_step.name, tool=workspace_step.tool) + if engine_flow.check_state( + name=workspace_step.name, tool=workspace_step.tool, state=StateEnum.Success + ): + metrics = build_step_metrics(workspace=workspace, step=workspace_step) if metrics is None: step_result.append(step["state"]) else: - index = 0 - for key, value in metrics.data.items(): - if init_header : + for index, (key, value) in enumerate(metrics.data.items()): + if init_header: header1.append(step["name"] if index == 0 else "") header2.append(key) - - index += 1 - - step_result.append(value) + + step_result.append(value) # step_result.append(step.get("state", "")) memory = step.get("peak memory (mb)", 0) - peak_memory = memory if memory>peak_memory else peak_memory - + peak_memory = memory if memory > peak_memory else peak_memory + # peak memory workspace_result.append(peak_memory) workspace_result.extend(step_result) init_header = False - + results.append(workspace_result) - + header = [header1, header2] - csv_write(file_path=statis_csv, - header=header, - data=results) \ No newline at end of file + csv_write(file_path=statis_csv, header=header, data=results) diff --git a/benchmark/get_parameters.py b/benchmark/get_parameters.py index 7a9d1380..a9922142 100644 --- a/benchmark/get_parameters.py +++ b/benchmark/get_parameters.py @@ -1,14 +1,15 @@ -import sys import os +import sys current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] +root = current_dir.rsplit("/", 1)[0] sys.path.append(root) -from chipcompiler.data import Parameters -from chipcompiler.data.parameter import get_parameters as _get_parameters +from chipcompiler.data import Parameters # noqa: E402 +from chipcompiler.data.parameter import get_parameters as _get_parameters # noqa: E402 + -def get_parameters(pdk_name : str, design : str = "", path : str = "") -> Parameters: +def get_parameters(pdk_name: str, design: str = "", path: str = "") -> Parameters: """ Return the Parameters instance based on the given pdk name. """ diff --git a/chipcompiler/analysis/step.py b/chipcompiler/analysis/step.py index 7e73ae05..4a496cda 100644 --- a/chipcompiler/analysis/step.py +++ b/chipcompiler/analysis/step.py @@ -1,24 +1,25 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StepMetrics, load_metrics, save_metrics +from chipcompiler.data import StepMetrics, Workspace, WorkspaceStep, load_metrics, save_metrics + class StepMetricsBuilder: """ A class to hold various analysis metrics for chip designs. """ + def __init__(self, workspace: Workspace): self.workspace = workspace - - def load(self, step : WorkspaceStep) -> StepMetrics: + + def load(self, step: WorkspaceStep) -> StepMetrics: """ Load step metrics from the step analysis metrics file. """ - step_metrics = load_metrics(step.analysis['metrics']) + step_metrics = load_metrics(step.analysis["metrics"]) return step_metrics - - def save(self, step: WorkspaceStep, step_metrics : StepMetrics) -> bool: + + def save(self, step: WorkspaceStep, step_metrics: StepMetrics) -> bool: """ save step metrics to the step analysis metrics file. """ - - save_metrics(step_metrics) \ No newline at end of file + + save_metrics(step_metrics) diff --git a/chipcompiler/data/__init__.py b/chipcompiler/data/__init__.py index 45e8c951..35378a21 100644 --- a/chipcompiler/data/__init__.py +++ b/chipcompiler/data/__init__.py @@ -1,51 +1,34 @@ +from .parameter import Parameters, get_parameters, load_parameter, save_parameter +from .pdk import PDK, get_pdk +from .step import CheckState, StateEnum, StepEnum, StepMetrics, load_metrics, save_metrics from .workspace import ( + OriginDesign, + Workspace, + WorkspaceStep, + create_default_sdc, create_workspace, load_workspace, - create_default_sdc, - Workspace, - WorkspaceStep, - OriginDesign, - log_workspace -) - -from .parameter import ( - Parameters, - load_parameter, - save_parameter, - get_parameters -) - -from .step import ( - StepEnum, - StateEnum, - CheckState, - StepMetrics, - load_metrics, - save_metrics -) - -from .pdk import ( - get_pdk, - PDK + log_workspace, ) __all__ = [ - 'create_workspace', - 'load_workspace', - 'create_default_sdc', - 'Workspace', - 'WorkspaceStep', - 'PDK', - 'OriginDesign', - 'log_workspace', - 'Parameters', - 'load_paramter', - 'save_parameter', - 'get_parameters', - 'StepEnum', - 'StateEnum', - 'CheckState', - 'StepMetrics', - 'load_metrics', - 'save_metrics' + "create_workspace", + "load_workspace", + "create_default_sdc", + "Workspace", + "WorkspaceStep", + "PDK", + "get_pdk", + "OriginDesign", + "log_workspace", + "Parameters", + "load_parameter", + "save_parameter", + "get_parameters", + "StepEnum", + "StateEnum", + "CheckState", + "StepMetrics", + "load_metrics", + "save_metrics", ] diff --git a/chipcompiler/data/parameter.py b/chipcompiler/data/parameter.py index a4af3a00..dd3c529a 100644 --- a/chipcompiler/data/parameter.py +++ b/chipcompiler/data/parameter.py @@ -1,31 +1,37 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os - from dataclasses import dataclass, field + @dataclass class Parameters: """ Dataclass for design parameters """ - path : str = "" # parameters file path - data : dict = field(default_factory=dict) # parameters data -def load_parameter(path : str) -> Parameters: + path: str = "" # parameters file path + data: dict = field(default_factory=dict) # parameters data + + +def load_parameter(path: str) -> Parameters: from chipcompiler.utility import json_read + parameter = Parameters() parameter.path = path parameter.data = json_read(path) return parameter - -def save_parameter(parameter : Parameters) -> bool: + + +def save_parameter(parameter: Parameters) -> bool: from chipcompiler.utility import json_write - return json_write(file_path=parameter.path, - data=parameter.data) -def get_parameters(pdk_name : str, design : str = "", path : str = "", current_dir : str = "") -> Parameters: + return json_write(file_path=parameter.path, data=parameter.data) + + +def get_parameters( + pdk_name: str, design: str = "", path: str = "", current_dir: str = "" +) -> Parameters: """ Return the Parameters instance based on the given pdk name. """ @@ -45,14 +51,17 @@ def get_parameters(pdk_name : str, design : str = "", path : str = "", current_d else: return Parameters() -def parameter_ics55(design : str, path : str, benchmark_json : str) -> Parameters: + +def parameter_ics55(design: str, path: str, benchmark_json: str) -> Parameters: parameters = Parameters() - + from chipcompiler.utility import json_read + parameters.path = path parameters.data = json_read(path) - + from chipcompiler.utility import json_read + if not os.path.isfile(benchmark_json): raise FileNotFoundError(f"ics55_benchmark.json not found: {benchmark_json}") benchmarks = json_read(benchmark_json) @@ -66,18 +75,20 @@ def parameter_ics55(design : str, path : str, benchmark_json : str) -> Parameter return parameters -def parameter_sky130(design : str, path : str) -> Parameters: + +def parameter_sky130(design: str, path: str) -> Parameters: parameters = Parameters() - + from chipcompiler.utility import json_read + parameters.path = path parameters.data = json_read(path) - + match design.lower(): case "gcd": parameters.data["Design"] = "gcd" parameters.data["Top module"] = "gcd" parameters.data["Clock"] = "clk" parameters.data["Frequency max [MHz]"] = 100 - + return parameters diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index a90300b4..f842a13e 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -1,35 +1,36 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from dataclasses import dataclass, field + @dataclass class PDK: """ Dataclass for PDK information """ - name : str = "" # pdk name - version : str = "" # pdk version - tech : str = "" # pdk tech lef file - lefs : list = field(default_factory=list) # pdk lef files - libs : list = field(default_factory=list) # pdk liberty files - sdc : str = "" # pdk sdc file - spef : str = "" # pdk spef file - site_core : str = "" # core site - site_io : str = "" # io site - site_corner : str = "" # corner site - tap_cell : str = "" # tap cell - end_cap : str = "" # end cap - buffers : list = field(default_factory=list) # buffers - fillers : list = field(default_factory=list) # fillers - tie_high_cell : str = "" - tie_high_port : str = "" - tie_low_cell : str = "" - tie_low_port : str = "" - dont_use : list = field(default_factory=list) # don't use cell list - - -def get_pdk(pdk_name : str) -> PDK: + + name: str = "" # pdk name + version: str = "" # pdk version + tech: str = "" # pdk tech lef file + lefs: list = field(default_factory=list) # pdk lef files + libs: list = field(default_factory=list) # pdk liberty files + sdc: str = "" # pdk sdc file + spef: str = "" # pdk spef file + site_core: str = "" # core site + site_io: str = "" # io site + site_corner: str = "" # corner site + tap_cell: str = "" # tap cell + end_cap: str = "" # end cap + buffers: list = field(default_factory=list) # buffers + fillers: list = field(default_factory=list) # fillers + tie_high_cell: str = "" + tie_high_port: str = "" + tie_low_cell: str = "" + tie_low_port: str = "" + dont_use: list = field(default_factory=list) # don't use cell list + + +def get_pdk(pdk_name: str) -> PDK: """ Return the PDK instance based on the given pdk name. """ @@ -40,50 +41,47 @@ def get_pdk(pdk_name : str) -> PDK: else: return PDK() + def PDK_ICS55() -> PDK: import os + current_dir = os.path.split(os.path.abspath(__file__))[0] - root = current_dir.rsplit('/', 2)[0] + root = current_dir.rsplit("/", 2)[0] - pdk_root = "{}/chipcompiler/thirdparty/icsprout55-pdk".format(root) - stdcell_dir = "{}/IP/STD_cell/ics55_LLSC_H7C_V1p10C100".format(pdk_root) + pdk_root = f"{root}/chipcompiler/thirdparty/icsprout55-pdk" + stdcell_dir = f"{pdk_root}/IP/STD_cell/ics55_LLSC_H7C_V1p10C100" pdk = PDK( name="ics55", version="V1p10C100", - tech="{}/prtech/techLEF/N551P6M.lef".format(pdk_root), - lefs = [ - "{}/ics55_LLSC_H7CR/lef/ics55_LLSC_H7CR_ecos.lef".format(stdcell_dir), - "{}/ics55_LLSC_H7CL/lef/ics55_LLSC_H7CL_ecos.lef".format(stdcell_dir) - ], - libs = [ - "{}/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) + tech=f"{pdk_root}/prtech/techLEF/N551P6M.lef", + lefs=[ + f"{stdcell_dir}/ics55_LLSC_H7CR/lef/ics55_LLSC_H7CR_ecos.lef", + f"{stdcell_dir}/ics55_LLSC_H7CL/lef/ics55_LLSC_H7CL_ecos.lef", ], - site_core = "core7", - site_io = "core7", - site_corner = "core7", - tap_cell = "FILLTAPH7R", - end_cap = "FILLTAPH7R", - buffers = [ - "BUFX8H7L", - "BUFX12H7L", - "BUFX16H7L", - "BUFX20H7L" + libs=[ + f"{stdcell_dir}/ics55_LLSC_H7CR/liberty/ics55_LLSC_H7CR_ss_rcworst_1p08_125_nldm.lib", + f"{stdcell_dir}/ics55_LLSC_H7CL/liberty/ics55_LLSC_H7CL_ss_rcworst_1p08_125_nldm.lib", ], - fillers = [ + site_core="core7", + site_io="core7", + site_corner="core7", + tap_cell="FILLTAPH7R", + end_cap="FILLTAPH7R", + buffers=["BUFX8H7L", "BUFX12H7L", "BUFX16H7L", "BUFX20H7L"], + fillers=[ "FILLER64H7R", "FILLER32H7R", "FILLER16H7R", "FILLER8H7R", "FILLER4H7R", "FILLER2H7R", - "FILLER1H7R" + "FILLER1H7R", ], - tie_high_cell = "TIEHIH7R", - tie_high_port = "Z", - tie_low_cell = "TIELOH7R", - tie_low_port = "Z", + tie_high_cell="TIEHIH7R", + tie_high_port="Z", + tie_low_cell="TIELOH7R", + tie_low_port="Z", dont_use=[ "DFFSRQX*", "DFFSRX*", @@ -95,83 +93,80 @@ def PDK_ICS55() -> PDK: "*OAI222*", "*OAI33*", "*NOR4*", - "ICG*" - ] + "ICG*", + ], ) return pdk + def PDK_SKY130() -> PDK: import os + current_dir = os.path.split(os.path.abspath(__file__))[0] - root = current_dir.rsplit('/', 2)[0] + root = current_dir.rsplit("/", 2)[0] + + foundry_dir = f"{root}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130" - foundry_dir = "{}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130".format(root) - pdk = PDK( name="sky130", version="v0.1", - tech="{}/lef/sky130_fd_sc_hs.tlef".format(foundry_dir), - lefs = [ - "{}/lef/sky130_fd_sc_hs_merged.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__com_bus_slice_10um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__com_bus_slice_1um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__com_bus_slice_20um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__com_bus_slice_5um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__connect_vcchib_vccd_and_vswitch_vddio_slice_20um.lef".format( - foundry_dir - ), - "{}/lef/sky130_ef_io__corner_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__disconnect_vccd_slice_5um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__disconnect_vdda_slice_5um.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__gpiov2_pad_wrapped.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vccd_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vccd_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vdda_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vdda_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vddio_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vddio_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssa_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssa_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssd_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssd_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssio_hvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_ef_io__vssio_lvc_pad.lef".format(foundry_dir), - "{}/lef/sky130_fd_io__top_xres4v2.lef".format(foundry_dir), - "{}/lef/sky130io_fill.lef".format(foundry_dir), - "{}/lef/sky130_sram_1rw1r_128x256_8.lef".format(foundry_dir), - "{}/lef/sky130_sram_1rw1r_44x64_8.lef".format(foundry_dir), - "{}/lef/sky130_sram_1rw1r_64x256_8.lef".format(foundry_dir), - "{}/lef/sky130_sram_1rw1r_80x64_8.lef".format(foundry_dir), - ], - libs = [ - "{}/lib/sky130_fd_sc_hs__tt_025C_1v80.lib".format(foundry_dir), - "{}/lib/sky130_dummy_io.lib".format(foundry_dir), - "{}/lib/sky130_sram_1rw1r_128x256_8_TT_1p8V_25C.lib".format(foundry_dir), - "{}/lib/sky130_sram_1rw1r_44x64_8_TT_1p8V_25C.lib".format(foundry_dir), - "{}/lib/sky130_sram_1rw1r_64x256_8_TT_1p8V_25C.lib".format(foundry_dir), - "{}/lib/sky130_sram_1rw1r_80x64_8_TT_1p8V_25C.lib".format(foundry_dir), + tech=f"{foundry_dir}/lef/sky130_fd_sc_hs.tlef", + lefs=[ + f"{foundry_dir}/lef/sky130_fd_sc_hs_merged.lef", + f"{foundry_dir}/lef/sky130_ef_io__com_bus_slice_10um.lef", + f"{foundry_dir}/lef/sky130_ef_io__com_bus_slice_1um.lef", + f"{foundry_dir}/lef/sky130_ef_io__com_bus_slice_20um.lef", + f"{foundry_dir}/lef/sky130_ef_io__com_bus_slice_5um.lef", + f"{foundry_dir}/lef/sky130_ef_io__connect_vcchib_vccd_and_vswitch_vddio_slice_20um.lef", + f"{foundry_dir}/lef/sky130_ef_io__corner_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__disconnect_vccd_slice_5um.lef", + f"{foundry_dir}/lef/sky130_ef_io__disconnect_vdda_slice_5um.lef", + f"{foundry_dir}/lef/sky130_ef_io__gpiov2_pad_wrapped.lef", + f"{foundry_dir}/lef/sky130_ef_io__vccd_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vccd_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vdda_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vdda_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vddio_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vddio_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssa_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssa_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssd_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssd_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssio_hvc_pad.lef", + f"{foundry_dir}/lef/sky130_ef_io__vssio_lvc_pad.lef", + f"{foundry_dir}/lef/sky130_fd_io__top_xres4v2.lef", + f"{foundry_dir}/lef/sky130io_fill.lef", + f"{foundry_dir}/lef/sky130_sram_1rw1r_128x256_8.lef", + f"{foundry_dir}/lef/sky130_sram_1rw1r_44x64_8.lef", + f"{foundry_dir}/lef/sky130_sram_1rw1r_64x256_8.lef", + f"{foundry_dir}/lef/sky130_sram_1rw1r_80x64_8.lef", ], - site_core = "unit", - site_io = "unit", - site_corner= "unit", - tap_cell = "sky130_fd_sc_hs__tap_1", - end_cap = "sky130_fd_sc_hs__fill_1", - buffers = [ - "sky130_fd_sc_hs__buf_1", - "sky130_fd_sc_hs__buf_8" + libs=[ + f"{foundry_dir}/lib/sky130_fd_sc_hs__tt_025C_1v80.lib", + f"{foundry_dir}/lib/sky130_dummy_io.lib", + f"{foundry_dir}/lib/sky130_sram_1rw1r_128x256_8_TT_1p8V_25C.lib", + f"{foundry_dir}/lib/sky130_sram_1rw1r_44x64_8_TT_1p8V_25C.lib", + f"{foundry_dir}/lib/sky130_sram_1rw1r_64x256_8_TT_1p8V_25C.lib", + f"{foundry_dir}/lib/sky130_sram_1rw1r_80x64_8_TT_1p8V_25C.lib", ], - fillers = [ + site_core="unit", + site_io="unit", + site_corner="unit", + tap_cell="sky130_fd_sc_hs__tap_1", + end_cap="sky130_fd_sc_hs__fill_1", + buffers=["sky130_fd_sc_hs__buf_1", "sky130_fd_sc_hs__buf_8"], + fillers=[ "sky130_fd_sc_hs__fill_8", "sky130_fd_sc_hs__fill_4", "sky130_fd_sc_hs__fill_2", - "sky130_fd_sc_hs__fill_1" + "sky130_fd_sc_hs__fill_1", ], - tie_high_cell = "sky130_fd_sc_hs__conb_1", - tie_high_port = "HI", - tie_low_cell = "sky130_fd_sc_hs__conb_1", - tie_low_port = "LO", - dont_use=[] + tie_high_cell="sky130_fd_sc_hs__conb_1", + tie_high_port="HI", + tie_low_cell="sky130_fd_sc_hs__conb_1", + tie_low_port="LO", + dont_use=[], ) - + return pdk diff --git a/chipcompiler/data/step.py b/chipcompiler/data/step.py index fed4dd1b..b5fe88e4 100644 --- a/chipcompiler/data/step.py +++ b/chipcompiler/data/step.py @@ -1,11 +1,12 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from enum import Enum from dataclasses import dataclass, field +from enum import Enum + class StepEnum(Enum): """RTL2GDS flow step names""" + RTL2GDS = "RTL2GDS" INIT = "Init" SOC = "SOC" @@ -23,7 +24,7 @@ class StepEnum(Enum): LEGALIZATION = "legalization" ROUTING = "route" FILLER = "filler" - GDS = "GDS" + GDS = "GDS" SIGNOFF = "Signoff" STA = "sta" DRC = "drc" @@ -31,23 +32,27 @@ class StepEnum(Enum): ABSTRACT_LEF = "Abstract lef" MERGE = "GDS merge" + class StateEnum(Enum): """flow running state""" - Invalid = "Invalid" # ecc tools or config invalid - Unstart = "Unstart" # step unstart - Success = "Success" # step run success - Ongoing = "Ongoing" # step is running - Pending = "Pending" # step is pending - Imcomplete = "Incomplete" # step is failed + + Invalid = "Invalid" # ecc tools or config invalid + Unstart = "Unstart" # step unstart + Success = "Success" # step run success + Ongoing = "Ongoing" # step is running + Pending = "Pending" # step is pending + Imcomplete = "Incomplete" # step is failed # Ignored = "Ignored" # step result do not affect flow step - + + class CheckState(Enum): """checklist state""" - Unstart = "Unstart" # checked unstart - Success = "Success" # checked success - Failed = "Failed" # checked Failed - Warning = "Warning" # checked Warning - + + Unstart = "Unstart" # checked unstart + Success = "Success" # checked success + Failed = "Failed" # checked Failed + Warning = "Warning" # checked Warning + ########################################################################### # step definition for chip design flow in json format @@ -61,14 +66,17 @@ class CheckState(Enum): # } ########################################################################### + @dataclass class StepMetrics: """ Dataclass for step metrics """ - path : str = "" # metrics file path - data : dict = field(default_factory=dict) # metrics data - report : list = field(default_factory=list) # metrics report + + path: str = "" # metrics file path + data: dict = field(default_factory=dict) # metrics data + report: list = field(default_factory=list) # metrics report + ########################################################################### # step metrics definition in json format @@ -79,14 +87,17 @@ class StepMetrics: # } ########################################################################### -def load_metrics(path : str) -> StepMetrics: + +def load_metrics(path: str) -> StepMetrics: from chipcompiler.utility import json_read - metrics = StepMetrics() + + metrics = StepMetrics() metrics.path = path metrics.data = json_read(path) return metrics -def save_metrics(metrics : StepMetrics) -> bool: + +def save_metrics(metrics: StepMetrics) -> bool: from chipcompiler.utility import json_write - return json_write(file_path=metrics.path, - data=metrics.data) \ No newline at end of file + + return json_write(file_path=metrics.path, data=metrics.data) diff --git a/chipcompiler/data/workspace.py b/chipcompiler/data/workspace.py index 890ea4da..be6af602 100644 --- a/chipcompiler/data/workspace.py +++ b/chipcompiler/data/workspace.py @@ -1,73 +1,83 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from dataclasses import dataclass, field -from .parameter import Parameters, save_parameter, load_parameter -from .pdk import get_pdk, PDK + from chipcompiler.utility import Logger, create_logger, dict_to_str, find_files -from chipcompiler.utility.filelist import parse_filelist, resolve_path, parse_incdir_directives - +from chipcompiler.utility.filelist import parse_filelist, parse_incdir_directives, resolve_path + +from .parameter import Parameters, load_parameter, save_parameter +from .pdk import PDK, get_pdk + + @dataclass class OriginDesign: """ Dataclass for original design information """ - name : str = "" # design name - top_module : str = "" # top module name - origin_def : str = "" # original def file path - origin_verilog : str = "" # original verilog file path - input_filelist : str = "" # input filelist for synthesis - + + name: str = "" # design name + top_module: str = "" # top module name + origin_def: str = "" # original def file path + origin_verilog: str = "" # original verilog file path + input_filelist: str = "" # input filelist for synthesis + + @dataclass class Flow: """ Dataclass for design flow """ - path : str = "" # flow file path - data : dict = field(default_factory=dict) # flow steps - + + path: str = "" # flow file path + data: dict = field(default_factory=dict) # flow steps + + @dataclass class Workspace: """ Dataclass for workspace information """ - directory : str = "" # workspace directory - design : OriginDesign = field(default_factory=OriginDesign) # original design info - pdk : PDK = field(default_factory=PDK) # pdk information - parameters : Parameters = field(default_factory=Parameters) # design parameters - flow : Flow = field(default_factory=Flow) # design flow for this workspace - + + directory: str = "" # workspace directory + design: OriginDesign = field(default_factory=OriginDesign) # original design info + pdk: PDK = field(default_factory=PDK) # pdk information + parameters: Parameters = field(default_factory=Parameters) # design parameters + flow: Flow = field(default_factory=Flow) # design flow for this workspace + # logger - logger : Logger = field(default_factory=Logger) # logger for this workspace - + logger: Logger = field(default_factory=Logger) # logger for this workspace + + @dataclass class WorkspaceStep: """ Dataclass for workspace step path information, describe all the info for this task step. """ + # step basic info - name : str = "" # step name - directory : str = "" # step working directory + name: str = "" # step name + directory: str = "" # step working directory # eda tool info - tool : str = "" # eda tool name - version : str = "" # eda tool version + tool: str = "" # eda tool name + 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 - feature : dict = field(default_factory=dict) # features path about this step - report : dict = field(default_factory=dict) # report path about this step - log : dict = field(default_factory=dict) # log path about this step - script : dict = field(default_factory=dict) # script path about this step - analysis : dict = field(default_factory=dict) # analysis path about this step - subflow : dict = field(default_factory=dict) # sub flow for this step - checklist : dict = field(default_factory=dict) # checklist 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 + feature: dict = field(default_factory=dict) # features path about this step + report: dict = field(default_factory=dict) # report path about this step + log: dict = field(default_factory=dict) # log path about this step + script: dict = field(default_factory=dict) # script path about this step + analysis: dict = field(default_factory=dict) # analysis path about this step + subflow: dict = field(default_factory=dict) # sub flow for this step + checklist: dict = field(default_factory=dict) # checklist for this step # step result info - result : dict = field(default_factory=dict) # result info about this step + result: dict = field(default_factory=dict) # result info about this step + def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=None) -> str: """ @@ -104,7 +114,7 @@ def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=N filelist_dir = os.path.dirname(os.path.abspath(input_filelist)) copied_files = set() - stats = {'copied': 0, 'missing': 0, 'incdir_copied': 0, 'incdir_skipped': 0} + stats = {"copied": 0, "missing": 0, "incdir_copied": 0, "incdir_skipped": 0} # Copy files listed in filelist try: @@ -120,7 +130,7 @@ def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=N if not os.path.exists(abs_src): if logger: logger.warning(f"File not found (skipping): {abs_src}") - stats['missing'] += 1 + stats["missing"] += 1 continue rel_path = os.path.basename(src_path) if os.path.isabs(src_path) else src_path @@ -132,7 +142,7 @@ def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=N if _copy_file_safely(abs_src, os.path.join(origin_dir, rel_path), logger, src_path): copied_files.add(rel_path) - stats['copied'] += 1 + stats["copied"] += 1 # Copy +incdir directories try: @@ -155,13 +165,13 @@ def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=N logger.warning(f"Include path is not a directory: {abs_incdir}") continue - for root, dirs, files in os.walk(abs_incdir): + for root, _dirs, files in os.walk(abs_incdir): for filename in files: src_file = os.path.join(root, filename) rel_from_filelist = os.path.relpath(src_file, filelist_dir) if rel_from_filelist in copied_files: - stats['incdir_skipped'] += 1 + stats["incdir_skipped"] += 1 if logger: logger.debug(f"Skipping duplicate from +incdir: {rel_from_filelist}") continue @@ -169,7 +179,7 @@ def copy_filelist_with_sources(input_filelist: str, workspace_dir: str, logger=N dst_file = os.path.join(origin_dir, rel_from_filelist) if _copy_file_safely(src_file, dst_file, logger, f"+incdir/{src_file}"): copied_files.add(rel_from_filelist) - stats['incdir_copied'] += 1 + stats["incdir_copied"] += 1 # Copy filelist file itself new_filelist = os.path.join(origin_dir, os.path.basename(input_filelist)) @@ -208,13 +218,15 @@ def _copy_file_safely(src: str, dst: str, logger, context: str) -> bool: logger.error(f"Error copying {context}: {e}") return False - -def create_workspace(directory : str, - origin_def : str, - origin_verilog : str, - pdk : PDK | str, - parameters : Parameters | dict, - input_filelist : str = "") -> Workspace: + +def create_workspace( + directory: str, + origin_def: str, + origin_verilog: str, + pdk: PDK | str, + parameters: Parameters | dict, + input_filelist: str = "", +) -> Workspace: """ Create a workspace for chip design flow. @@ -237,44 +249,47 @@ def create_workspace(directory : str, """ # create workspace directory import os + try: os.makedirs(directory, exist_ok=True) - except OSError as error: + except OSError: return None - + # create workspace instance workspace = Workspace() - - # pdk + + # pdk if isinstance(pdk, PDK): workspace.pdk = pdk - + if isinstance(pdk, str): - workspace.pdk = get_pdk(pdk) - - #update config + workspace.pdk = get_pdk(pdk) + + # update config if isinstance(parameters, Parameters): workspace.design.name = parameters.data["Design"] - workspace.design.top_module = parameters.data["Top module"] + workspace.design.top_module = parameters.data["Top module"] workspace.parameters = parameters - + if isinstance(parameters, dict): workspace.design.name = parameters["Design"] - workspace.design.top_module = parameters["Top module"] + workspace.design.top_module = parameters["Top module"] workspace.parameters.data = parameters - + # update path workspace.directory = directory workspace.parameters.path = f"{directory}/parameters.json" workspace.flow.path = f"{directory}/flow.json" - + # create logger first (needed for copy operations) os.makedirs(f"{directory}/log", exist_ok=True) - workspace.logger = create_logger(name=workspace.parameters.data["Design"], - log_dir=f"{directory}/log") + workspace.logger = create_logger( + name=workspace.parameters.data["Design"], log_dir=f"{directory}/log" + ) # update orign files to workspace origin folder import shutil + os.makedirs(f"{directory}/origin", exist_ok=True) if os.path.exists(origin_def): shutil.copy(origin_def, f"{directory}/origin/{os.path.basename(origin_def)}") @@ -293,16 +308,16 @@ def create_workspace(directory : str, try: # Use new copy_filelist_with_sources to copy filelist + all RTL files workspace.design.input_filelist = copy_filelist_with_sources( - input_filelist=input_filelist, - workspace_dir=directory, - logger=workspace.logger + input_filelist=input_filelist, workspace_dir=directory, logger=workspace.logger ) except Exception as e: workspace.logger.error(f"Failed to copy filelist sources: {e}") workspace.logger.warning("Falling back to copying only filelist file") # Fallback: copy only filelist file (backward compatibility) shutil.copy(input_filelist, f"{directory}/origin/{os.path.basename(input_filelist)}") - workspace.design.input_filelist = f"{directory}/origin/{os.path.basename(input_filelist)}" + workspace.design.input_filelist = ( + f"{directory}/origin/{os.path.basename(input_filelist)}" + ) if os.path.exists(workspace.pdk.sdc): shutil.copy(workspace.pdk.sdc, f"{directory}/origin/{os.path.basename(workspace.pdk.sdc)}") @@ -310,30 +325,35 @@ def create_workspace(directory : str, else: # create default sdc file from .workspace import create_default_sdc + workspace.pdk.sdc = f"{directory}/origin/{workspace.design.name}.sdc" create_default_sdc(workspace) - + if os.path.exists(workspace.pdk.spef): - shutil.copy(workspace.pdk.spef, f"{directory}/origin/{os.path.basename(workspace.pdk.spef)}") + 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)}" # save parameter save_parameter(workspace.parameters) - + return workspace -def load_workspace(directory : str) -> Workspace: + +def load_workspace(directory: str) -> Workspace: import os + if not os.path.exists(directory): return None - + # create workspace instance workspace = Workspace() workspace.directory = directory parameters = load_parameter(f"{directory}/parameters.json") workspace.parameters = parameters - + pdk = get_pdk(pdk_name=parameters.data.get("PDK", "")) sdc_path = find_files(f"{directory}/origin", ".sdc") if len(sdc_path) > 0: @@ -341,43 +361,43 @@ def load_workspace(directory : str) -> Workspace: spef_path = find_files(f"{directory}/origin", ".spef") if len(spef_path) > 0: pdk.spef = spef_path[0] - + workspace.pdk = pdk - - #update config + + # update config workspace.design.name = parameters.data.get("Design", "") - workspace.design.top_module = parameters.data.get("Top module", "") + workspace.design.top_module = parameters.data.get("Top module", "") def_path = find_files(f"{directory}/origin", ".def") def_gz_path = find_files(f"{directory}/origin", ".def.gz") if len(def_path) > 0: workspace.design.origin_def = def_path[0] if len(def_gz_path) > 0: workspace.design.origin_def = def_gz_path[0] - + verilog_path = find_files(f"{directory}/origin", ".v") verilog_gz_path = find_files(f"{directory}/origin", ".v.gz") if len(verilog_path) > 0: workspace.design.origin_verilog = verilog_path[0] if len(verilog_gz_path) > 0: workspace.design.origin_verilog = verilog_gz_path[0] - + filelist_path = f"{directory}/origin/filelist" if os.path.exists(filelist_path): workspace.design.input_filelist = filelist_path - + # update path workspace.flow.path = f"{directory}/flow.json" - + # create logger first (needed for copy operations) - workspace.logger = create_logger(name=parameters.data["Design"], - log_dir=f"{directory}/log") + workspace.logger = create_logger(name=parameters.data["Design"], log_dir=f"{directory}/log") return workspace -def log_workspace(workspace : Workspace): - def format_string(text : str, len=20) -> str: + +def log_workspace(workspace: Workspace): + def format_string(text: str, len=20) -> str: return text.ljust(len, " ") - + workspace.logger.info("######################################################################") workspace.logger.info("workspace : %s", workspace.directory) workspace.logger.info("PDK : %s", workspace.pdk.name) @@ -397,20 +417,25 @@ def format_string(text : str, len=20) -> str: workspace.logger.info("") workspace.logger.info("######################################################################") workspace.logger.info("flow : %s", workspace.flow.path) - workspace.logger.info("%s | %s | %s | %s", - format_string("name"), - format_string("tool"), - format_string("state"), - format_string("runtime")) + workspace.logger.info( + "%s | %s | %s | %s", + format_string("name"), + format_string("tool"), + format_string("state"), + format_string("runtime"), + ) for step in workspace.flow.data.get("steps", []): - workspace.logger.info("%s | %s | %s | %s", - format_string(step.get("name", "")), - format_string(step.get("tool", "")), - format_string(step.get("state", "")), - format_string(step.get("runtime", ""))) + workspace.logger.info( + "%s | %s | %s | %s", + format_string(step.get("name", "")), + format_string(step.get("tool", "")), + format_string(step.get("state", "")), + format_string(step.get("runtime", "")), + ) workspace.logger.info("######################################################################") -def create_default_sdc(workspace : Workspace): + +def create_default_sdc(workspace: Workspace): """ Create SDC file based on PDK and workspace parameters. """ @@ -419,11 +444,13 @@ def create_default_sdc(workspace : Workspace): sdc_content.append("\n") sdc_content.append("set clk_name {} \n".format(workspace.parameters.data.get("Clock", ""))) sdc_content.append("set clk_port_name {}\n".format(workspace.parameters.data.get("Clock", ""))) - sdc_content.append("set clk_freq_mhz {}\n".format(workspace.parameters.data.get("Frequency max [MHz]", 100))) + sdc_content.append( + "set clk_freq_mhz {}\n".format(workspace.parameters.data.get("Frequency max [MHz]", 100)) + ) sdc_content.append("set clk_period [expr 1000.0 / $clk_freq_mhz]\n") sdc_content.append("set clk_io_pct 0.2\n") sdc_content.append("set clk_port [get_ports $clk_port_name]\n") sdc_content.append("create_clock -name $clk_name -period $clk_period $clk_port\n") - - with open(workspace.pdk.sdc, 'w') as file: - file.writelines(sdc_content) \ No newline at end of file + + with open(workspace.pdk.sdc, "w") as file: + file.writelines(sdc_content) diff --git a/chipcompiler/engine/__init__.py b/chipcompiler/engine/__init__.py index b6f0a2de..f3aedb92 100644 --- a/chipcompiler/engine/__init__.py +++ b/chipcompiler/engine/__init__.py @@ -1,12 +1,4 @@ -from .db import ( - EngineDB -) +from .db import EngineDB +from .flow import EngineFlow -from .flow import ( - EngineFlow -) - -__all__ = [ - 'EngineDB', - 'EngineFlow' -] \ No newline at end of file +__all__ = ["EngineDB", "EngineFlow"] diff --git a/chipcompiler/engine/db.py b/chipcompiler/engine/db.py index 0899f1dc..7b402040 100644 --- a/chipcompiler/engine/db.py +++ b/chipcompiler/engine/db.py @@ -1,17 +1,19 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from chipcompiler.data import Workspace, WorkspaceStep + class EngineDB: """ this class is for ECC lifecycle management 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, eda: ECCToolsModule = None): self.workspace = workspace self.eda = eda - + @property def engine(self): return self.eda @@ -22,28 +24,24 @@ def create_db_engine(self, step: WorkspaceStep) -> bool: """ if self.eda is not None: return True - + # check eda tool exist from chipcompiler.tools import load_eda_module + eda_module = load_eda_module("ecc") if eda_module is None: return False - + from chipcompiler.tools.ecc import create_db_engine + self.eda = create_db_engine(self.workspace, step) - if self.eda is not None: - return True - else: - return False - - def update_db_from_step(self, - step : WorkspaceStep): + return self.eda is not None + + def update_db_from_step(self, step: WorkspaceStep): """ - update data after step finished, + update data after step finished, update data by read the def or verilog, parsing nessary infomation, - for example, + for example, if step is "place", read instances data from step output def file and update to db egine. """ - def_file = step.output["def"] - - self.eda.read_def() \ No newline at end of file + self.eda.read_def() diff --git a/chipcompiler/engine/flow.py b/chipcompiler/engine/flow.py index 085b2c8f..3f8c44ad 100644 --- a/chipcompiler/engine/flow.py +++ b/chipcompiler/engine/flow.py @@ -1,22 +1,22 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os import time from multiprocessing import Process - -from chipcompiler.data import Workspace, WorkspaceStep, StateEnum, StepEnum + +from chipcompiler.data import StateEnum, StepEnum, Workspace, WorkspaceStep from chipcompiler.engine import EngineDB from chipcompiler.utility import track_process_memory + class EngineFlow: - def __init__(self, workspace : Workspace): + def __init__(self, workspace: Workspace): self.workspace = workspace self.workspace_steps = [] - self.db = None # db engine for this flow - + self.db = None # db engine for this flow + self.load() - + def build_default_steps(self): # Flow step sequences steps = [] @@ -33,97 +33,81 @@ def build_default_steps(self): steps.append(self.init_flow_step(StepEnum.FILLER, "ecc", StateEnum.Unstart)) # steps.append(self.init_flow_step(StepEnum.GDS, "klayout", StateEnum.Unstart)) # steps.append(self.init_flow_step(StepEnum.SIGNOFF, "ecc", StateEnum.Unstart)) - - self.workspace.flow.data = {"steps" : steps} - + + self.workspace.flow.data = {"steps": steps} + self.save() - + def has_init(self): - return True if len(self.workspace.flow.data.get("steps", [])) > 0 else False - - def init_flow_step(self, - step : StepEnum | str, - tool : str, - state : str | StateEnum): + return len(self.workspace.flow.data.get("steps", [])) > 0 + + def init_flow_step(self, step: StepEnum | str, tool: str, state: str | StateEnum): step_value = step.value if isinstance(step, StepEnum) else step state_value = state.value if isinstance(state, StateEnum) else state return { - "name" : step_value, # step name - "tool" : tool, # eda tool name - "state" : state_value, # step state - "runtime" : "", # step run time - "peak memory (mb)" : 0, # step peak memory - "info" : {} # step additional infomation + "name": step_value, # step name + "tool": tool, # eda tool name + "state": state_value, # step state + "runtime": "", # step run time + "peak memory (mb)": 0, # step peak memory + "info": {}, # step additional infomation } - - def add_step(self, - step : StepEnum | str, - tool : str, - state : str | StateEnum): + + def add_step(self, step: StepEnum | str, tool: str, state: str | StateEnum): steps = self.workspace.flow.data.get("steps", []) steps.append(self.init_flow_step(step, tool, state)) - - self.workspace.flow.data = {"steps" : steps} - + + self.workspace.flow.data = {"steps": steps} + self.save() - + def load(self) -> bool: """ load flow config json from workspace """ from chipcompiler.utility import json_read + self.workspace.flow.data = json_read(self.workspace.flow.path) - if len(self.workspace.flow.data.get("steps", [])) <= 0: - return False + return len(self.workspace.flow.data.get("steps", [])) > 0 - return True - def save(self) -> bool: """ save flow to workspace json """ from chipcompiler.utility import json_write - return json_write(self.workspace.flow.path, - self.workspace.flow.data) - - def get_step(self, - name : str, - tool : str): + + return json_write(self.workspace.flow.path, self.workspace.flow.data) + + def get_step(self, name: str, tool: str): for step in self.workspace.flow.data.get("steps", []): if step.get("name") == name and step.get("tool") == tool: return step - + return None - - def get_workspace_step(self, - name : str): + + def get_workspace_step(self, name: str): for workspace_step in self.workspace_steps: if workspace_step.name == name: return workspace_step - + return None - - def check_state(self, - name : str, - tool : str, - state : str | StateEnum): + + def check_state(self, name: str, tool: str, state: str | StateEnum): """ return True if step state has been set """ step = self.get_step(name, tool) state_value = state.value if isinstance(state, StateEnum) else state - if step is not None \ - and step.get("state") == state_value: - return True - - return False - - def set_state(self, - name : str, - tool : str, - state : str | StateEnum, - runtime : str=None, - peak_memory : float=None) -> bool: + return step is not None and step.get("state") == state_value + + def set_state( + self, + name: str, + tool: str, + state: str | StateEnum, + runtime: str = None, + peak_memory: float = None, + ) -> bool: state_value = state.value if isinstance(state, StateEnum) else state for step in self.workspace.flow.data.get("steps", []): if step.get("name") == name and step.get("tool") == tool: @@ -135,44 +119,46 @@ def set_state(self, self.save() return True - + return False - + def clear_states(self): from chipcompiler.data import StateEnum + for step in self.workspace.flow.data.get("steps", []): step["state"] = StateEnum.Unstart.value step["runtime"] = "" step["peak memory (mb)"] = 0 - + self.save() - + def is_flow_success(self): """ check all steps success """ from chipcompiler.data import StateEnum + for step in self.workspace.flow.data.get("steps", []): - if(step["state"] != StateEnum.Success.value): + if step["state"] != StateEnum.Success.value: return False - + return True - - def check_step_result(self, - workspace_step : WorkspaceStep): + + def check_step_result(self, workspace_step: WorkspaceStep): """ check step output exist """ - import os success = False match workspace_step.name: case StepEnum.SYNTHESIS.value: if os.path.exists(workspace_step.output.get("verilog", "")): success = True - case default: - if os.path.exists(workspace_step.output.get("def", "")) and \ - os.path.exists(workspace_step.output.get("verilog", "")) and \ - os.path.exists(workspace_step.output.get("gds", "")): + case _: + if ( + os.path.exists(workspace_step.output.get("def", "")) + and os.path.exists(workspace_step.output.get("verilog", "")) + and os.path.exists(workspace_step.output.get("gds", "")) + ): success = True return success @@ -190,14 +176,17 @@ def create_step_workspaces(self): # use the output def and verilog from last step. input_def = pre_step.output["def"] input_verilog = pre_step.output["verilog"] - - from chipcompiler.tools import create_step, run_step + + from chipcompiler.tools import create_step + # create workspace step - eda_step = create_step(workspace=self.workspace, - step=step["name"], - eda=step["tool"], - input_def=input_def, - input_verilog=input_verilog) + eda_step = create_step( + workspace=self.workspace, + step=step["name"], + eda=step["tool"], + input_def=input_def, + input_verilog=input_verilog, + ) # save workspace step if eda_step is not None: self.workspace_steps.append(eda_step) @@ -205,39 +194,36 @@ def create_step_workspaces(self): else: # error create step, TBD pass - + def init_db_engine(self) -> bool: if len(self.workspace_steps) <= 0: return False - + if self.db is not None: return True - + # init engine step by last workpsace step data if all step run success workspace_step = self.workspace_steps[-1] for ws_step in self.workspace_steps: - if not self.check_state(name=ws_step.name, - tool=ws_step.tool, - state=StateEnum.Success): + 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 - + engine = EngineDB(workspace=self.workspace) if engine.create_db_engine(step=workspace_step): self.db = engine return True - else: - return False - - + + return False + def run_steps(self, rerun=False) -> bool: """ run all flow steps """ - for workspace_step in self.workspace_steps: + for workspace_step in self.workspace_steps: state = self.run_step(workspace_step, rerun) - - match(state): + + match state: case StateEnum.Success: continue case StateEnum.Invalid: @@ -250,12 +236,10 @@ def run_steps(self, rerun=False) -> bool: return False case StateEnum.Ongoing: return False - + return True - - def run_step(self, - workspace_step : WorkspaceStep | str, - rerun : bool = False) -> StateEnum: + + def run_step(self, workspace_step: WorkspaceStep | str, rerun: bool = False) -> StateEnum: """ run single step """ @@ -263,62 +247,69 @@ def run_step(self, workspace_step = self.get_workspace_step(workspace_step) if workspace_step is None: return StateEnum.Invalid - - if not rerun and self.check_state(name=workspace_step.name, - tool=workspace_step.tool, - state=StateEnum.Success): + + if not rerun and self.check_state( + name=workspace_step.name, tool=workspace_step.tool, state=StateEnum.Success + ): return StateEnum.Success - - from chipcompiler.tools import create_step, run_step - - #set timer + + from chipcompiler.tools import run_step + + # set timer start_time = time.time() - + # set state ongoing - self.set_state(name=workspace_step.name, - tool=workspace_step.tool, - state=StateEnum.Ongoing) - + self.set_state(name=workspace_step.name, tool=workspace_step.tool, state=StateEnum.Ongoing) + # run step in a separate process p = Process(target=run_step, args=(self.workspace, workspace_step)) p.start() - + # track memory usage in another thread from threading import Thread - + # track memory usage peak_memory_result = [0] + def memory_tracker_wrapper(): peak_memory_result[0] = track_process_memory(p.pid) - + tracker_thread = Thread(target=memory_tracker_wrapper, daemon=True) tracker_thread.start() # wait for step process to finish p.join() # get peak memory usage tracker_thread.join(timeout=1.0) - + # get peak memory in mb peak_memory_mb = round(peak_memory_result[0] / 1024.0, 3) - + # end time end_time = time.time() elapsed_time = end_time - start_time - runtime = "{}:{}:{}".format(int(elapsed_time // 3600), - int((elapsed_time % 3600) // 60), - int(elapsed_time % 60)) - + runtime = ( + f"{int(elapsed_time // 3600)}:" + f"{int((elapsed_time % 3600) // 60)}:" + f"{int(elapsed_time % 60)}" + ) + # save state - state=StateEnum.Success if self.check_step_result(workspace_step=workspace_step) else StateEnum.Imcomplete - self.set_state(name=workspace_step.name, - tool=workspace_step.tool, - state=state, - runtime=runtime, - peak_memory=peak_memory_mb) - + state = ( + StateEnum.Success + if self.check_step_result(workspace_step=workspace_step) + else StateEnum.Imcomplete + ) + self.set_state( + name=workspace_step.name, + tool=workspace_step.tool, + state=state, + runtime=runtime, + peak_memory=peak_memory_mb, + ) + if state == StateEnum.Success: from chipcompiler.tools import save_layout_image - save_layout_image(workspace=self.workspace, - step=workspace_step) - - return state \ No newline at end of file + + save_layout_image(workspace=self.workspace, step=workspace_step) + + return state diff --git a/chipcompiler/rtl2gds/__init__.py b/chipcompiler/rtl2gds/__init__.py index a0cf941a..73b12450 100644 --- a/chipcompiler/rtl2gds/__init__.py +++ b/chipcompiler/rtl2gds/__init__.py @@ -1,5 +1,3 @@ from .builder import build_rtl2gds_flow -__all__ = [ - 'build_rtl2gds_flow' -] +__all__ = ["build_rtl2gds_flow"] diff --git a/chipcompiler/rtl2gds/builder.py b/chipcompiler/rtl2gds/builder.py index 78a6ff0d..4bf98001 100644 --- a/chipcompiler/rtl2gds/builder.py +++ b/chipcompiler/rtl2gds/builder.py @@ -1,15 +1,12 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import ( - StepEnum, - StateEnum -) +from chipcompiler.data import StateEnum, StepEnum + def build_rtl2gds_flow() -> list: steps = [] - - steps.append((StepEnum.SYNTHESIS, "yosys", StateEnum.Unstart)) + + steps.append((StepEnum.SYNTHESIS, "yosys", StateEnum.Unstart)) steps.append((StepEnum.FLOORPLAN, "ecc", StateEnum.Unstart)) steps.append((StepEnum.NETLIST_OPT, "ecc", StateEnum.Unstart)) steps.append((StepEnum.PLACEMENT, "ecc", StateEnum.Unstart)) @@ -18,5 +15,5 @@ def build_rtl2gds_flow() -> list: steps.append((StepEnum.ROUTING, "ecc", StateEnum.Unstart)) steps.append((StepEnum.DRC, "ecc", StateEnum.Unstart)) steps.append((StepEnum.FILLER, "ecc", StateEnum.Unstart)) - + return steps diff --git a/chipcompiler/services/__init__.py b/chipcompiler/services/__init__.py index 63e9b135..53ae5569 100644 --- a/chipcompiler/services/__init__.py +++ b/chipcompiler/services/__init__.py @@ -1,27 +1,17 @@ from .main import app from .routers import workspace_router -from .schemas import ( - CMDEnum, - ResponseEnum, - DATA_TEMPLATE, - ECCRequest, - ECCResponse, - InfoEnum -) -from .services import ( - ECCService, - ecc_service -) +from .schemas import DATA_TEMPLATE, CMDEnum, ECCRequest, ECCResponse, InfoEnum, ResponseEnum +from .services import ECCService, ecc_service __all__ = [ - 'app', - 'workspace_router', - 'CMDEnum', - 'ResponseEnum', - 'DATA_TEMPLATE', - 'ECCRequest', - 'ECCResponse', - 'InfoEnum', - 'ECCService', - 'ecc_service' -] \ No newline at end of file + "app", + "workspace_router", + "CMDEnum", + "ResponseEnum", + "DATA_TEMPLATE", + "ECCRequest", + "ECCResponse", + "InfoEnum", + "ECCService", + "ecc_service", +] diff --git a/chipcompiler/services/main.py b/chipcompiler/services/main.py index b0c72642..ea169df3 100644 --- a/chipcompiler/services/main.py +++ b/chipcompiler/services/main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -8,22 +7,20 @@ # Create FastAPI application app = FastAPI( - title="ChipCompiler API", - description="Backend API for ChipCompiler EDA tool", - version="0.1.0" + title="ChipCompiler API", description="Backend API for ChipCompiler EDA tool", version="0.1.0" ) # Configure CORS for frontend access app.add_middleware( CORSMiddleware, allow_origins=[ - "http://localhost:1420", # Tauri dev server + "http://localhost:1420", # Tauri dev server "http://127.0.0.1:1420", - "http://localhost:5173", # Vite dev server + "http://localhost:5173", # Vite dev server "http://127.0.0.1:5173", - "tauri://localhost", # Tauri production (v1 style) - "https://tauri.localhost", # Tauri v2 production - "http://tauri.localhost", # Tauri v2 production (http) + "tauri://localhost", # Tauri production (v1 style) + "https://tauri.localhost", # Tauri v2 production + "http://tauri.localhost", # Tauri v2 production (http) ], allow_credentials=True, allow_methods=["*"], @@ -37,11 +34,7 @@ @app.get("/") async def root(): """Root endpoint""" - return { - "name": "ChipCompiler API", - "version": "0.1.0", - "status": "running" - } + return {"name": "ChipCompiler API", "version": "0.1.0", "status": "running"} @app.get("/health") diff --git a/chipcompiler/services/routers/__init__.py b/chipcompiler/services/routers/__init__.py index 9c904702..ff9623cc 100644 --- a/chipcompiler/services/routers/__init__.py +++ b/chipcompiler/services/routers/__init__.py @@ -1,3 +1,3 @@ from .ecc import router as workspace_router -__all__ = ['workspace_router'] +__all__ = ["workspace_router"] diff --git a/chipcompiler/services/routers/ecc.py b/chipcompiler/services/routers/ecc.py index 0514b817..2b6d3efa 100644 --- a/chipcompiler/services/routers/ecc.py +++ b/chipcompiler/services/routers/ecc.py @@ -1,23 +1,21 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter -from ..schemas import ( - ECCRequest, - ECCResponse -) +from ..schemas import ECCRequest, ECCResponse from ..services import ecc_service ecc_serv = ecc_service() router = APIRouter(prefix="/api/workspace", tags=["workspace"]) + @router.get("/health") async def health_check(): """Health check endpoint""" return {"status": "ok"} + @router.post("/create_workspace", response_model=ECCResponse) async def create_workspace(request: ECCRequest): """ @@ -25,6 +23,7 @@ async def create_workspace(request: ECCRequest): """ return ecc_serv.create_workspace(request) + @router.post("/load_workspace", response_model=ECCResponse) async def load_workspace(request: ECCRequest): """ @@ -32,6 +31,7 @@ async def load_workspace(request: ECCRequest): """ return ecc_serv.load_workspace(request) + @router.post("/delete_workspace", response_model=ECCResponse) async def delete_workspace(request: ECCRequest): """ @@ -39,6 +39,7 @@ async def delete_workspace(request: ECCRequest): """ return ecc_serv.delete_workspace(request) + @router.post("/rtl2gds", response_model=ECCResponse) async def rtl2gds(request: ECCRequest): """ @@ -46,6 +47,7 @@ async def rtl2gds(request: ECCRequest): """ return ecc_serv.rtl2gds(request) + @router.post("/run_step", response_model=ECCResponse) async def run_step(request: ECCRequest): """ @@ -53,9 +55,10 @@ async def run_step(request: ECCRequest): """ return ecc_serv.run_step(request) + @router.post("/get_info", response_model=ECCResponse) async def get_info(request: ECCRequest): """ get information by step and id. """ - return ecc_serv.get_info(request) \ No newline at end of file + return ecc_serv.get_info(request) diff --git a/chipcompiler/services/run_server.py b/chipcompiler/services/run_server.py index a1603418..6a2121e4 100644 --- a/chipcompiler/services/run_server.py +++ b/chipcompiler/services/run_server.py @@ -1,13 +1,15 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- """ Standalone script to run the FastAPI server. This script is intended to be spawned by Tauri at application startup. """ -import sys +import argparse import os +import sys + +import uvicorn # Add project root to path for imports script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -15,39 +17,23 @@ if project_root not in sys.path: sys.path.insert(0, project_root) -import argparse -import uvicorn - def main(): parser = argparse.ArgumentParser(description="Run ChipCompiler API server") - parser.add_argument( - "--host", - default="127.0.0.1", - help="Host to bind to (default: 127.0.0.1)" - ) - parser.add_argument( - "--port", - type=int, - default=8765, - help="Port to bind to (default: 8765)" - ) - parser.add_argument( - "--reload", - action="store_true", - help="Enable auto-reload for development" - ) - + parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)") + parser.add_argument("--port", type=int, default=8765, help="Port to bind to (default: 8765)") + parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development") + args = parser.parse_args() - + print(f"Starting ChipCompiler API server on {args.host}:{args.port}") - + uvicorn.run( "chipcompiler.services.main:app", host=args.host, port=args.port, reload=args.reload, - log_level="info" + log_level="info", ) diff --git a/chipcompiler/services/schemas/__init__.py b/chipcompiler/services/schemas/__init__.py index 05c8e502..d50d3c9b 100644 --- a/chipcompiler/services/schemas/__init__.py +++ b/chipcompiler/services/schemas/__init__.py @@ -1,20 +1,4 @@ -from .ecc import ( - CMDEnum, - ResponseEnum, - DATA_TEMPLATE, - ECCRequest, - ECCResponse -) +from .ecc import DATA_TEMPLATE, CMDEnum, ECCRequest, ECCResponse, ResponseEnum +from .info import InfoEnum -from .info import ( - InfoEnum -) - -__all__ = [ - 'CMDEnum', - 'ResponseEnum', - 'DATA_TEMPLATE', - 'ECCRequest', - 'ECCResponse', - 'InfoEnum' -] +__all__ = ["CMDEnum", "ResponseEnum", "DATA_TEMPLATE", "ECCRequest", "ECCResponse", "InfoEnum"] diff --git a/chipcompiler/services/schemas/ecc.py b/chipcompiler/services/schemas/ecc.py index 9acfaf80..81fa9ecc 100644 --- a/chipcompiler/services/schemas/ecc.py +++ b/chipcompiler/services/schemas/ecc.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from pydantic import BaseModel from enum import Enum -from typing import List + +from pydantic import BaseModel + class CMDEnum(Enum): create_workspace = "create_workspace" @@ -13,88 +13,51 @@ class CMDEnum(Enum): run_step = "run_step" get_info = "get_info" + class ResponseEnum(Enum): success = "success" failed = "failed" error = "error" warning = "warning" + DATA_TEMPLATE = { - "create_workspace" : { - "requeset" : { - "directory" : "", - "pdk" : "", - "parameters" : {}, - "origin_def" : "", - "origin_verilog" : "", - "filelist" : "" + "create_workspace": { + "requeset": { + "directory": "", + "pdk": "", + "parameters": {}, + "origin_def": "", + "origin_verilog": "", + "filelist": "", }, - "response" : { - "directory" : "" - } + "response": {"directory": ""}, }, - - "load_workspace" : { - "requeset" : { - "directory" : "" - }, - "response" : { - "directory" : "" - } + "load_workspace": {"requeset": {"directory": ""}, "response": {"directory": ""}}, + "delete_workspace": {"requeset": {"directory": ""}, "response": {"directory": ""}}, + "rtl2gds": {"requeset": {"rerun": False}, "response": {"rerun": False}}, + "run_step": { + "requeset": {"step": "", "rerun": False}, + "response": {"step": "", "state": "Unstart"}, }, - - "delete_workspace" : { - "requeset" : { - "directory" : "" - }, - "response" : { - "directory" : "" - } - }, - - "rtl2gds" : { - "requeset" : { - "rerun" : False - }, - "response" : { - "rerun" : False - } - }, - - "run_step" : { - "requeset" : { - "step" : "", - "rerun" : False - }, - "response" : { - "step" : "", - "state" : "Unstart" - } - }, - - "get_info" : { - "requeset" : { - "step" : "", - "id" : "" - }, - "response" : { - "step" : "", - "id" : "", - "info" : {} - } + "get_info": { + "requeset": {"step": "", "id": ""}, + "response": {"step": "", "id": "", "info": {}}, }, } + class ECCRequest(BaseModel): - """ - """ - cmd : str - data : dict - + """ """ + + cmd: str + data: dict + + class ECCResponse(BaseModel): - """ - """ - cmd : str - response : str - data : dict - message : list \ No newline at end of file + """ """ + + cmd: str + response: str + data: dict + message: list diff --git a/chipcompiler/services/schemas/info.py b/chipcompiler/services/schemas/info.py index e41cf989..2e05bd75 100644 --- a/chipcompiler/services/schemas/info.py +++ b/chipcompiler/services/schemas/info.py @@ -1,13 +1,13 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from enum import Enum + class InfoEnum(Enum): - views = "views" # information while switch web page - layout = "layout" # step design layout - metrics = "metrics" # step metrics - subflow = "subflow" # sub steps for this step - analysis = "analysis" # analysis metrics - maps = "maps" # maps for this step such as density map - checklist = "checklist" # step checklist \ No newline at end of file + views = "views" # information while switch web page + layout = "layout" # step design layout + metrics = "metrics" # step metrics + subflow = "subflow" # sub steps for this step + analysis = "analysis" # analysis metrics + maps = "maps" # maps for this step such as density map + checklist = "checklist" # step checklist diff --git a/chipcompiler/services/services/__init__.py b/chipcompiler/services/services/__init__.py index 38af06d6..ccd40b93 100644 --- a/chipcompiler/services/services/__init__.py +++ b/chipcompiler/services/services/__init__.py @@ -4,12 +4,10 @@ global _ecc_service _ecc_service = ECCService() + def ecc_service(): global _ecc_service return _ecc_service -__all__ = [ - 'ECCService', - 'ecc_service', - 'get_info' -] \ No newline at end of file + +__all__ = ["ECCService", "ecc_service", "get_step_info"] diff --git a/chipcompiler/services/services/ecc.py b/chipcompiler/services/services/ecc.py index d51ce9f8..98ef7031 100644 --- a/chipcompiler/services/services/ecc.py +++ b/chipcompiler/services/services/ecc.py @@ -1,55 +1,34 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os -from chipcompiler.data import ( - create_workspace, - load_workspace, - StepEnum, - StateEnum, - PDK, - get_pdk -) - -from chipcompiler.engine import ( - EngineDB, - EngineFlow -) - +from chipcompiler.data import StateEnum, create_workspace, load_workspace +from chipcompiler.engine import EngineFlow from chipcompiler.rtl2gds import build_rtl2gds_flow +from chipcompiler.services.schemas import CMDEnum, ECCRequest, ECCResponse, ResponseEnum -from benchmark import get_parameters - -from chipcompiler.services.schemas import ( - CMDEnum, - ECCRequest, - ECCResponse, - ResponseEnum, - DATA_TEMPLATE - ) class ECCService: def __init__(self): self.workspace = None self.engine_flow = None - - def check_cmd(self, request: ECCRequest, cmd : CMDEnum): + + def check_cmd(self, request: ECCRequest, cmd: CMDEnum): # print cmd # print(request) - + # check cmd if request.cmd != cmd.value: response = ECCResponse( cmd=request.cmd, response=ResponseEnum.failed.value, data={}, - message = [].append(f"requese cmd not match {request.cmd}") + message=[].append(f"requese cmd not match {request.cmd}"), ) - + return False, response - + return True, None - + def __build_flow(self): engine_flow = EngineFlow(workspace=self.workspace) if not engine_flow.has_init(): @@ -58,172 +37,165 @@ def __build_flow(self): engine_flow.add_step(step=step, tool=tool, state=state) else: engine_flow.create_step_workspaces() - + self.engine_flow = engine_flow - + if engine_flow.is_flow_success(): - return + return engine_flow.create_step_workspaces() - + def create_workspace(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.create_workspace) if not state: - return response - + return response + # get data data = request.data - + # check data - + # process cmd - workspace = create_workspace(directory=data.get("directory", ""), - pdk=data.get("pdk", ""), - parameters=data.get("parameters", {}), - origin_def=data.get("origin_def", ""), - origin_verilog=data.get("origin_verilog", ""), - input_filelist=data.get("filelist", "")) - + workspace = create_workspace( + directory=data.get("directory", ""), + pdk=data.get("pdk", ""), + parameters=data.get("parameters", {}), + origin_def=data.get("origin_def", ""), + origin_verilog=data.get("origin_verilog", ""), + input_filelist=data.get("filelist", ""), + ) + if workspace is None: return ECCResponse( cmd=request.cmd, response=ResponseEnum.failed.value, data={}, - message = [f"create workspace failed : {data.get('directory', '')}"] + message=[f"create workspace failed : {data.get('directory', '')}"], ) else: self.workspace = workspace self.__build_flow() - - response_data = { - "directory" : data.get("directory", "") - } + + response_data = {"directory": data.get("directory", "")} return ECCResponse( cmd=request.cmd, response=ResponseEnum.success.value, data=response_data, - message = [f"create workspace success : {data.get('directory', '')}"] + message=[f"create workspace success : {data.get('directory', '')}"], ) - + def load_workspace(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.load_workspace) if not state: - return response - + return response + # get data data = request.data - + # check data - + # process cmd workspace = load_workspace(directory=data.get("directory", "")) - + if workspace is None: return ECCResponse( cmd=request.cmd, response=ResponseEnum.failed.value, data={}, - message = [f"load workspace failed : {data.get('directory', '')}"] + message=[f"load workspace failed : {data.get('directory', '')}"], ) else: self.workspace = workspace self.__build_flow() - - response_data = { - "directory" : data.get("directory", "") - } + + response_data = {"directory": data.get("directory", "")} return ECCResponse( cmd=request.cmd, response=ResponseEnum.success.value, data=response_data, - message = [f"load workspace success : {data.get('directory', '')}"] + message=[f"load workspace success : {data.get('directory', '')}"], ) - + def delete_workspace(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.delete_workspace) if not state: - return response - + return response + # get data data = request.data - directory = data.get('directory', '') - + directory = data.get("directory", "") + # check data - if self.workspace is None \ - or self.workspace.directory != directory \ - or not os.path.exists(directory): + if ( + self.workspace is None + or self.workspace.directory != directory + or not os.path.exists(directory) + ): return ECCResponse( cmd=request.cmd, response=ResponseEnum.error.value, data={}, - message = [f"workspace not exist : {directory}"] + message=[f"workspace not exist : {directory}"], ) - + # process cmd self.engine_flow = None self.workspace = None try: import shutil + shutil.rmtree(directory) - except Exception as e: + except Exception: pass - - response_data = { - "directory" : directory - } + + response_data = {"directory": directory} return ECCResponse( cmd=request.cmd, response=ResponseEnum.success.value, data=response_data, - message = [f"delete workspace success : {directory}"] + message=[f"delete workspace success : {directory}"], ) - + def rtl2gds(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.rtl2gds) if not state: - return response - + return response + # get data data = request.data - - response_data = { - "rerun" : data.get("rerun", False) - } - + + response_data = {"rerun": data.get("rerun", False)} + # check data - if self.workspace is None \ - or not os.path.exists(self.workspace.directory): + if self.workspace is None or not os.path.exists(self.workspace.directory): return ECCResponse( cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"workspace not exist : {self.workspace.directory}"] + message=[f"workspace not exist : {self.workspace.directory}"], ) - + if self.engine_flow is None: return ECCResponse( cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"rtl2gds flow not exist : {self.workspace.directory}"] + message=[f"rtl2gds flow not exist : {self.workspace.directory}"], ) - + # process cmd failed_step = None try: if data.get("rerun", False): self.engine_flow.clear_states() - + for workspace_step in self.engine_flow.workspace_steps: ecc_req = ECCRequest( - cmd = "run_step", - data = { - "step" : workspace_step.name, - "rerun" : data.get("rerun", False) - } + cmd="run_step", + data={"step": workspace_step.name, "rerun": data.get("rerun", False)}, ) # get response for each step # TBD, need to send response back to gui @@ -237,117 +209,110 @@ def rtl2gds(self, request: ECCRequest) -> ECCResponse: cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"run rtl2gds failed : {e}"] + message=[f"run rtl2gds failed : {e}"], ) - + if failed_step is None: return ECCResponse( cmd=request.cmd, response=ResponseEnum.success.value, data=response_data, - message = [f"run rtl2gds success : {self.workspace.directory}"] + message=[f"run rtl2gds success : {self.workspace.directory}"], ) else: return ECCResponse( cmd=request.cmd, response=ResponseEnum.failed.value, data=response_data, - message = [f"run rtl2gds failed in step : {failed_step}"] + message=[f"run rtl2gds failed in step : {failed_step}"], ) - + def run_step(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.run_step) if not state: - return response - + return response + # get data data = request.data step = data.get("step", "") rerun = data.get("rerun", "") - - response_data = { - "step" : step, - "state" : "Unstart" - } - + + response_data = {"step": step, "state": "Unstart"} + # check data - if self.workspace is None \ - or not os.path.exists(self.workspace.directory): + if self.workspace is None or not os.path.exists(self.workspace.directory): return ECCResponse( cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"workspace not exist : {self.workspace.directory}"] + message=[f"workspace not exist : {self.workspace.directory}"], ) - + # process cmd state = StateEnum.Unstart try: state = self.engine_flow.run_step(step, rerun) - except Exception as e: + except Exception: state = StateEnum.Imcomplete pass - + response_data["state"] = state.value - + if StateEnum.Success == state: return ECCResponse( cmd=request.cmd, response=ResponseEnum.success.value, data=response_data, - message = [f"run step {step} success : {self.workspace.directory}"] + message=[f"run step {step} success : {self.workspace.directory}"], ) else: return ECCResponse( cmd=request.cmd, response=ResponseEnum.failed.value, data=response_data, - message = [f"run step {step} failed with state {state.value} : {self.workspace.directory}"] + message=[ + f"run step {step} failed with state {state.value} : {self.workspace.directory}" + ], ) - + def get_info(self, request: ECCRequest) -> ECCResponse: # check cmd state, response = self.check_cmd(request, CMDEnum.get_info) if not state: - return response - + return response + # get data data = request.data step = data.get("step", "") id = data.get("id", "") - - - response_data = { - "step" : step, - "id" : id, - "info" : {} - } - + + response_data = {"step": step, "id": id, "info": {}} + # check data - if self.workspace is None \ - or not os.path.exists(self.workspace.directory): + if self.workspace is None or not os.path.exists(self.workspace.directory): return ECCResponse( cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"workspace not exist : {self.workspace.directory}"] + message=[f"workspace not exist : {self.workspace.directory}"], ) - + # process cmd try: # build information from .info import get_step_info - info = get_step_info(workspace=self.workspace, - step=self.engine_flow.get_workspace_step(step), - id=id) - + + info = get_step_info( + workspace=self.workspace, step=self.engine_flow.get_workspace_step(step), id=id + ) + if len(info) == 0: return ECCResponse( cmd=request.cmd, response=ResponseEnum.warning.value, data=response_data, - message = [f"no information for step {step} : {self.workspace.directory}"] + message=[f"no information for step {step} : {self.workspace.directory}"], ) else: response_data["info"] = info @@ -356,14 +321,12 @@ def get_info(self, request: ECCRequest) -> ECCResponse: cmd=request.cmd, response=ResponseEnum.error.value, data=response_data, - message = [f"get information error for step {step} : {e}"] + message=[f"get information error for step {step} : {e}"], ) - + return ECCResponse( - cmd=request.cmd, - response=ResponseEnum.success.value, - data=response_data, - message = [f"get information success : {step} - {id}"] - ) - - + cmd=request.cmd, + response=ResponseEnum.success.value, + data=response_data, + message=[f"get information success : {step} - {id}"], + ) diff --git a/chipcompiler/services/services/info.py b/chipcompiler/services/services/info.py index 3dbf2a8e..0ab39f29 100644 --- a/chipcompiler/services/services/info.py +++ b/chipcompiler/services/services/info.py @@ -1,13 +1,9 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from enum import Enum from chipcompiler.data import Workspace, WorkspaceStep -def get_step_info(workspace: Workspace, - step: WorkspaceStep, - id : str) -> dict: + +def get_step_info(workspace: Workspace, step: WorkspaceStep, id: str) -> dict: from chipcompiler.tools import get_step_info - return get_step_info(workspace=workspace, - step=step, - id=id) \ No newline at end of file + + return get_step_info(workspace=workspace, step=step, id=id) diff --git a/chipcompiler/tools/__init__.py b/chipcompiler/tools/__init__.py index ef9f71f7..65a7414b 100644 --- a/chipcompiler/tools/__init__.py +++ b/chipcompiler/tools/__init__.py @@ -1,17 +1,17 @@ from .eda import ( - load_eda_module, + build_step_metrics, create_step, + get_step_info, + load_eda_module, run_step, save_layout_image, - build_step_metrics, - get_step_info ) __all__ = [ - 'load_eda_module', - 'create_step', - 'run_step', - 'save_layout_image', - 'build_step_metrics', - 'get_step_info' -] \ No newline at end of file + "load_eda_module", + "create_step", + "run_step", + "save_layout_image", + "build_step_metrics", + "get_step_info", +] diff --git a/chipcompiler/tools/ecc/__init__.py b/chipcompiler/tools/ecc/__init__.py index 230a5a6c..2f5762ca 100644 --- a/chipcompiler/tools/ecc/__init__.py +++ b/chipcompiler/tools/ecc/__init__.py @@ -1,45 +1,25 @@ -from .builder import ( - build_step, - build_step_space, - build_step_config -) - -from .runner import ( - create_db_engine, - run_step -) - +from .builder import build_step, build_step_config, build_step_space +from .checklist import EccChecklist +from .metrics import build_step_metrics from .module import ECCToolsModule - from .plot import ECCToolsPlot - -from .metrics import ( - build_step_metrics -) - -from .service import( - get_step_info -) - +from .runner import create_db_engine, run_step +from .service import get_step_info from .subflow import EccSubFlow - -from .checklist import EccChecklist - -from .utility import ( - is_eda_exist -) +from .utility import is_eda_exist __all__ = [ - 'is_eda_exist', - 'build_default_flow', - 'build_step', - 'build_step_space', - 'build_step_config', - 'run_step', - 'create_db_engine', - 'ECCToolsModule', - 'ECCToolsPlot', - 'build_step_metrics', - 'get_step_info', - 'EccSubFlow' -] \ No newline at end of file + "is_eda_exist", + # "build_default_flow", + "build_step", + "build_step_space", + "build_step_config", + "run_step", + "create_db_engine", + "ECCToolsModule", + "ECCToolsPlot", + "build_step_metrics", + "get_step_info", + "EccSubFlow", + "EccChecklist", +] diff --git a/chipcompiler/tools/ecc/builder.py b/chipcompiler/tools/ecc/builder.py index dd0a3874..d0c9fada 100644 --- a/chipcompiler/tools/ecc/builder.py +++ b/chipcompiler/tools/ecc/builder.py @@ -1,19 +1,22 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os -from chipcompiler.data import WorkspaceStep, Workspace, Parameters, StepEnum, StateEnum - -def build_step(workspace: Workspace, - step_name: str, - input_def : str, - input_verilog : str, - output_def : str = None, - output_verilog : str = None, - output_gds : str = None) -> WorkspaceStep: + +from chipcompiler.data import StepEnum, Workspace, WorkspaceStep + + +def build_step( + workspace: Workspace, + step_name: str, + input_def: str, + input_verilog: str, + output_def: str = None, + output_verilog: str = None, + output_gds: str = None, +) -> WorkspaceStep: """ Build the given step in the specified workspace. """ - + step = WorkspaceStep() step.name = step_name step.tool = "ecc" @@ -21,8 +24,8 @@ def build_step(workspace: Workspace, # build step directory step.directory = f"{workspace.directory}/{step.name}_{step.tool}" - - # build config paths + + # build config paths step.config = { "dir": f"{step.directory}/config", "flow": f"{step.directory}/config/flow_config.json", @@ -30,23 +33,24 @@ def build_step(workspace: Workspace, 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.NETLIST_OPT.value}": os.path.join( + 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.TIMING_OPT_SETUP.value}": os.path.join( + 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.FILLER.value}": f"{step.directory}/config/pl_default_config.json", } - + # build input paths - step.input = { - "def": input_def, - "verilog": input_verilog - } - + step.input = {"def": input_def, "verilog": input_verilog} + # build output paths if output_def is None: output_def = f"{step.directory}/output/{workspace.design.name}_{step.name}.def.gz" @@ -60,9 +64,9 @@ def build_step(workspace: Workspace, "def": output_def, "verilog": output_verilog, "gds": output_gds, - "image": output_image + "image": output_image, } - + # build data paths step.data = { "dir": f"{step.directory}/data", @@ -78,9 +82,9 @@ def build_step(workspace: Workspace, f"{StepEnum.TIMING_OPT_SETUP.value}": f"{step.directory}/data/to", f"{StepEnum.ROUTING.value}": f"{step.directory}/data/rt", f"{StepEnum.STA.value}": f"{step.directory}/data/sta", - f"{StepEnum.DRC.value}": f"{step.directory}/data/drc" + f"{StepEnum.DRC.value}": f"{step.directory}/data/drc", } - + # build feature paths step.feature = { "dir": f"{step.directory}/feature", @@ -88,69 +92,61 @@ def build_step(workspace: Workspace, "step": f"{step.directory}/feature/{step.name}.step.json", "map": f"{step.directory}/feature/{step.name}.map.json", } - + # build report paths step.report = { "dir": f"{step.directory}/report", "db": f"{step.directory}/report/{step.name}.db.rpt", - "step": f"{step.directory}/report/{step.name}.rpt" + "step": f"{step.directory}/report/{step.name}.rpt", } - + # build log paths - step.log = { - "dir": f"{step.directory}/log", - "file": f"{step.directory}/log/{step.name}.log" - } - + step.log = {"dir": f"{step.directory}/log", "file": f"{step.directory}/log/{step.name}.log"} + # build script paths step.script = { "dir": f"{step.directory}/script", - "main": f"{step.directory}/script/{step.name}_main.tcl" + "main": f"{step.directory}/script/{step.name}_main.tcl", } - + # build analysis paths step.analysis = { "dir": f"{step.directory}/analysis", "metrics": f"{step.directory}/analysis/{step.name}_metrics.json", - "statis_csv": f"{step.directory}/analysis/{step.name}_statis.csv" - } - + "statis_csv": f"{step.directory}/analysis/{step.name}_statis.csv", + } + # build sub flow paths - step.subflow = { - "path": f"{step.directory}/subflow.json", - "steps": [] - } - + step.subflow = {"path": f"{step.directory}/subflow.json", "steps": []} + # build checklist paths and data - step.checklist = { - "path": f"{step.directory}/checklist.json", - "checklist": [] - } - + step.checklist = {"path": f"{step.directory}/checklist.json", "checklist": []} + return step -def build_sub_flow(workspace : Workspace, - workspace_step : WorkspaceStep): + +def build_sub_flow(workspace: Workspace, workspace_step: WorkspaceStep): from .subflow import EccSubFlow - subflow = EccSubFlow(workspace=workspace, - workspace_step=workspace_step) - - subflow.build_sub_flow() - -def build_checklist(workspace : Workspace, - workspace_step : WorkspaceStep): + + subflow = EccSubFlow(workspace=workspace, workspace_step=workspace_step) + + subflow.build_sub_flow() + + +def build_checklist(workspace: Workspace, workspace_step: WorkspaceStep): from .checklist import EccChecklist - checklist = EccChecklist(workspace=workspace, - workspace_step=workspace_step) - - checklist.build_checklist() + + checklist = EccChecklist(workspace=workspace, workspace_step=workspace_step) + + checklist.build_checklist() + def build_step_space(step: WorkspaceStep) -> None: """ Create the workspace directories for the given step. """ 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) @@ -160,38 +156,35 @@ def build_step_space(step: WorkspaceStep) -> None: os.makedirs(step.log.get("dir", f"{step.directory}/log"), exist_ok=True) os.makedirs(step.script.get("dir", f"{step.directory}/script"), exist_ok=True) os.makedirs(step.analysis.get("dir", f"{step.directory}/analysis"), exist_ok=True) - + # build data directory - for key, dir in step.data.items(): + for _key, dir in step.data.items(): os.makedirs(dir, exist_ok=True) - + # create pl sub dir os.makedirs(f"{step.directory}/data/pl/density", exist_ok=True) os.makedirs(f"{step.directory}/data/pl/gui", exist_ok=True) os.makedirs(f"{step.directory}/data/pl/log", exist_ok=True) os.makedirs(f"{step.directory}/data/pl/plot", exist_ok=True) - os.makedirs(f"{step.directory}/data/pl/report", exist_ok=True) - + os.makedirs(f"{step.directory}/data/pl/report", exist_ok=True) + -def build_step_config(workspace: Workspace, - step: WorkspaceStep): +def build_step_config(workspace: Workspace, step: WorkspaceStep): """ Build the configuration files for the given step based on the parameters. """ # build subflow json - build_sub_flow(workspace=workspace, - workspace_step=step) - - build_checklist(workspace=workspace, - workspace_step=step) - + build_sub_flow(workspace=workspace, workspace_step=step) + + build_checklist(workspace=workspace, workspace_step=step) + # update config by parameters from chipcompiler.utility import json_read, json_write - + 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}"] @@ -201,14 +194,14 @@ def _update_flow(): 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 @@ -218,103 +211,106 @@ def _update_db(): 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", "") - + 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 - + # 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 if len(workspace.pdk.buffers) > 0: config["root_buffer_type"] = workspace.pdk.buffers[0] else: config["root_buffer_type"] = "" - + 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) - + # copy files to origin folder import shutil + # Get the directory of the current script current_dir = os.path.dirname(os.path.abspath(__file__)) - default_dir = os.path.abspath(os.path.join(current_dir, 'configs')) + default_dir = os.path.abspath(os.path.join(current_dir, "configs")) shutil.copytree(default_dir, step.config["dir"], dirs_exist_ok=True) - - _update_flow() + + _update_flow() _update_db() _update_fixfanout() _update_placement() @@ -322,4 +318,4 @@ def _update_router(): _update_drv() _update_hold() _update_setup() - _update_router() \ No newline at end of file + _update_router() diff --git a/chipcompiler/tools/ecc/checklist.py b/chipcompiler/tools/ecc/checklist.py index 0243a957..a3e2c14b 100644 --- a/chipcompiler/tools/ecc/checklist.py +++ b/chipcompiler/tools/ecc/checklist.py @@ -1,16 +1,17 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StateEnum, StepEnum, CheckState +from chipcompiler.data import CheckState, StepEnum, Workspace, WorkspaceStep + class EccChecklist: - def __init__(self, workspace : Workspace, workspace_step: WorkspaceStep): + def __init__(self, workspace: Workspace, workspace_step: WorkspaceStep): self.workspace = workspace self.workspace_step = workspace_step - + self.init_checklist() - + def init_checklist(self): from chipcompiler.utility import json_read + data = json_read(self.workspace_step.checklist.get("path", "")) if len(data) > 0: self.workspace_step.checklist["checklist"] = data.get("checklist", []) @@ -18,21 +19,21 @@ def init_checklist(self): self.build_checklist() def build_checklist(self) -> list: - def checklist_template(check_item : str, - description : str): + def checklist_template(check_item: str, description: str): return { - "name" : check_item, - "description" : description, - "state" : CheckState.Unstart.value - } - + "name": check_item, + "description": description, + "state": CheckState.Unstart.value, + } + checklist = [] - + step = StepEnum(self.workspace_step.name) match step: case StepEnum.FLOORPLAN: - checklist.append(checklist_template(check_item="Die Area", - description="check DIE area")) + checklist.append( + checklist_template(check_item="Die Area", description="check DIE area") + ) case StepEnum.NETLIST_OPT: pass @@ -54,27 +55,27 @@ def checklist_template(check_item : str, pass case StepEnum.DRC: pass - + self.workspace_step.checklist["checklist"] = checklist - + self.save() - + def save(self) -> bool: from chipcompiler.utility import json_write - - return json_write(file_path=self.workspace_step.checklist.get("path", ""), - data=self.workspace_step.checklist) - - def update_item(self, - check_item : str, - state : str | CheckState): + + return json_write( + file_path=self.workspace_step.checklist.get("path", ""), + data=self.workspace_step.checklist, + ) + + def update_item(self, check_item: str, state: str | CheckState): state = state.value if isinstance(state, CheckState) else state - + for item_dict in self.workspace_step.checklist.get("checklist", []): if item_dict.get("name") == check_item: item_dict["state"] = state self.save() - + def check(self): - pass \ No newline at end of file + pass diff --git a/chipcompiler/tools/ecc/metrics.py b/chipcompiler/tools/ecc/metrics.py index f33a2fb4..116b57c5 100644 --- a/chipcompiler/tools/ecc/metrics.py +++ b/chipcompiler/tools/ecc/metrics.py @@ -1,26 +1,23 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from chipcompiler.data import ( - Workspace, - WorkspaceStep, - StepMetrics, - save_metrics, + StateEnum, StepEnum, - StateEnum + StepMetrics, + Workspace, + WorkspaceStep, + save_metrics, ) -from chipcompiler.utility import json_read - from chipcompiler.tools.ecc.subflow import EccSubFlow +from chipcompiler.utility import json_read -def build_step_metrics(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_step_metrics(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return a StepMetrics instance for the given workspace step. """ # step matrics metrics = None - match(step.name): + match step.name: case StepEnum.FLOORPLAN.value: metrics = build_metrics_floorplan(workspace, step) case StepEnum.NETLIST_OPT.value: @@ -41,89 +38,95 @@ def build_step_metrics(workspace: Workspace, metrics = build_metrics_drc(workspace, step) case StepEnum.FILLER.value: metrics = build_metrics_filler(workspace, step) - + # update sub flow metrics state - sub_flow = EccSubFlow(workspace=workspace, - workspace_step=step) - sub_flow.update_step(step_name="analysis", - state=StateEnum.Invalid if metrics is None else StateEnum.Success) - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + sub_flow.update_step( + step_name="analysis", state=StateEnum.Invalid if metrics is None else StateEnum.Success + ) + return metrics -def build_metrics_db(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_metrics_db(workspace: Workspace, step: WorkspaceStep) -> dict: # db summary matrics metrics = {} - data = json_read(step.feature.get('db', "")) + data = json_read(step.feature.get("db", "")) if len(data) > 0: - metrics["Die area [μm^2]"] = f"{round(data.get('Design Layout', {}).get('die_area', 0.0), 3)}" - metrics["Die width [um]"] = f"{data.get('Design Layout', {}).get('die_bounding_width', 0.0)}" - metrics["Die height [um]"] = f"{data.get('Design Layout', {}).get('die_bounding_height', 0.0)}" + metrics["Die area [μm^2]"] = ( + f"{round(data.get('Design Layout', {}).get('die_area', 0.0), 3)}" + ) + metrics["Die width [um]"] = ( + f"{data.get('Design Layout', {}).get('die_bounding_width', 0.0)}" + ) + metrics["Die height [um]"] = ( + f"{data.get('Design Layout', {}).get('die_bounding_height', 0.0)}" + ) metrics["Die util"] = f"{round(data.get('Design Layout', {}).get('die_usage', 0.0), 2)}" metrics["Core util"] = f"{round(data.get('Design Layout', {}).get('core_usage', 0.0), 2)}" - metrics["Total io pins"] = data.get('Design Statis', {}).get('num_iopins', 0) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - metrics["Total nets"] = data.get('Design Statis', {}).get('num_nets', 0) + metrics["Total io pins"] = data.get("Design Statis", {}).get("num_iopins", 0) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + metrics["Total nets"] = data.get("Design Statis", {}).get("num_nets", 0) return metrics -def build_metrics_floorplan(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: + +def build_metrics_floorplan(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return floorplan metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} - metrics['Design'] = workspace.design.name - metrics['Step'] = step.name - metrics['Tool'] = step.tool - + metrics["Design"] = workspace.design.name + metrics["Step"] = step.name + metrics["Tool"] = step.tool + # db summary matrics metrics.update(build_metrics_db(workspace, step)) - + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: # Add floorplan specific metrics here pass - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None + -def build_metrics_net_opt(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_net_opt(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return net operation metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - data = json_read(step.feature.get('db', "")) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - metrics["Total nets"] = data.get('Design Statis', {}).get('num_nets', 0) - + data = json_read(step.feature.get("db", "")) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + metrics["Total nets"] = data.get("Design Statis", {}).get("num_nets", 0) + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: metrics["Max fanout"] = workspace.parameters.data.get("Max fanout", 0) @@ -134,279 +137,273 @@ def build_metrics_net_opt(workspace: Workspace, metrics["setup_tns"] = clk_item.get("opt_setup_tns", 0) metrics["hold_wns"] = clk_item.get("opt_hold_wns", 0) metrics["hold_tns"] = clk_item.get("opt_hold_tns", 0) - + break - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_filler(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_filler(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return filler metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - data = json_read(step.feature.get('db', "")) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - + data = json_read(step.feature.get("db", "")) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: # Add filler specific metrics here pass - + step_metrics.data = metrics - + # generate report image and dscription - image_path = json_path.replace(".json", ".png") + image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_drc(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_drc(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return DRC metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: metrics["drc_num"] = data.get("drc", {}).get("number", 0) - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_routing(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_routing(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return routing metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - + # step matrics - json_path = step.feature.get('db', "") + json_path = step.feature.get("db", "") data = json_read(json_path) if len(data) > 0: metrics["wire_len"] = data.get("Nets", {}).get("wire_len", 0) metrics["num_via"] = data.get("Nets", {}).get("num_via", 0) - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_legalization(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_legalization(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return legalization metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: metrics["total_movement"] = data.get("legalization", {}).get("total_movement", 0) - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_timing_opt_hold(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_timing_opt_hold(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return timing optimization (hold) metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - data = json_read(step.feature.get('db', "")) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - metrics["Total nets"] = data.get('Design Statis', {}).get('num_nets', 0) - + data = json_read(step.feature.get("db", "")) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + metrics["Total nets"] = data.get("Design Statis", {}).get("num_nets", 0) + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: for clk_item in data.get("optHold", {}).get("clocks_timing", []): metrics["suggest_freq"] = clk_item.get("opt_suggest_freq", 0) metrics["hold_wns"] = clk_item.get("opt_wns", 0) metrics["hold_tns"] = clk_item.get("opt_tns", 0) - + break - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_timing_opt_drv(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_timing_opt_drv(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return timing optimization (driver) metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - data = json_read(step.feature.get('db', "")) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - metrics["Total nets"] = data.get('Design Statis', {}).get('num_nets', 0) - + data = json_read(step.feature.get("db", "")) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + metrics["Total nets"] = data.get("Design Statis", {}).get("num_nets", 0) + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: for clk_item in data.get("optDrv", {}).get("clocks_timing", []): metrics["suggest_freq"] = clk_item.get("opt_suggest_freq", 0) metrics["wns"] = clk_item.get("opt_wns", 0) metrics["tns"] = clk_item.get("opt_tns", 0) - + break - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None + -def build_metrics_cts(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_cts(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return CTS metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - data = json_read(step.feature.get('db', "")) - metrics["Total instances"] = data.get('Design Statis', {}).get('num_instances', 0) - metrics["Total nets"] = data.get('Design Statis', {}).get('num_nets', 0) - + data = json_read(step.feature.get("db", "")) + metrics["Total instances"] = data.get("Design Statis", {}).get("num_instances", 0) + metrics["Total nets"] = data.get("Design Statis", {}).get("num_nets", 0) + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: metrics["buffer_num"] = data.get("CTS", {}).get("buffer_num", 0) @@ -414,48 +411,47 @@ def build_metrics_cts(workspace: Workspace, metrics["clock_path_max_buffer"] = data.get("CTS", {}).get("clock_path_max_buffer", 0) metrics["clock_path_min_buffer"] = data.get("CTS", {}).get("clock_path_min_buffer", 0) metrics["total_clock_wirelength"] = data.get("CTS", {}).get("total_clock_wirelength", 0) - + for clk_item in data.get("CTS", {}).get("clocks_timing", []): metrics["suggest_freq"] = clk_item.get("suggest_freq", 0) metrics["hold_wns"] = clk_item.get("hold_wns", 0) metrics["hold_tns"] = clk_item.get("hold_tns", 0) metrics["setup_wns"] = clk_item.get("setup_wns", 0) metrics["setup_tns"] = clk_item.get("setup_tns", 0) - + break - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None + return None -def build_metrics_placement(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_metrics_placement(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and return placement metrics dictionary. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis['metrics'] - + step_metrics.path = step.analysis["metrics"] + metrics = {} # metrics['Design'] = workspace.design.name # metrics['Step'] = step.name # metrics['Tool'] = step.tool - + # db summary matrics # metrics.update(build_metrics_db(workspace, step)) - + # step matrics - json_path = step.feature.get('step', "") + json_path = step.feature.get("step", "") data = json_read(json_path) if len(data) > 0: metrics["overflow"] = data.get("place", {}).get("overflow", 0) @@ -463,16 +459,16 @@ def build_metrics_placement(workspace: Workspace, metrics["bin_number"] = data.get("place", {}).get("bin_number", 0) metrics["GP HPWL"] = data.get("place", {}).get("gplace", {}).get("HPWL", 0) / 1000 metrics["DP HPWL"] = data.get("place", {}).get("dplace", {}).get("STWL", 0) / 1000 - + step_metrics.data = metrics - + # generate report image and dscription image_path = json_path.replace(".json", ".png") report = f"{step.name} step metrics:\n" - + step_metrics.report.append((image_path, report)) - + if save_metrics(step_metrics): return step_metrics else: - return None \ No newline at end of file + return None diff --git a/chipcompiler/tools/ecc/module.py b/chipcompiler/tools/ecc/module.py index 682523f4..396f4f3b 100644 --- a/chipcompiler/tools/ecc/module.py +++ b/chipcompiler/tools/ecc/module.py @@ -1,73 +1,62 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from numpy import double + class ECCToolsModule: """ python api package of ECC. """ + def __init__(self): try: from chipcompiler.tools.ecc.utility import is_eda_exist + if is_eda_exist(): from chipcompiler.tools.ecc.bin import ecc_py as ecc - except ImportError: - raise ImportError("ecc tool is not installed or not found.") - + except ImportError as err: + raise ImportError("ecc tool is not installed or not found.") from err + self.ecc = ecc def get_ecc(self): return self.ecc - + def exit(self): """exit ECC tools""" self.ecc.flow_exit() - + ######################################################################## # config api ######################################################################## - def init_config(self, - flow_config : str, - db_config : str, - output_dir : str, - feature_dir : str): + def init_config(self, flow_config: str, db_config: str, output_dir: str, feature_dir: str): """init_config""" - self.ecc.flow_init( - flow_config=flow_config - ) + self.ecc.flow_init(flow_config=flow_config) self.ecc.db_init( config_path=db_config, output_path=output_dir, feature_path=feature_dir, ) - + ######################################################################## # data api ######################################################################## - def set_net(self, - net_name: str, - net_type: str): + def set_net(self, net_name: str, net_type: str): """ set net type """ return self.ecc.set_net(net_name=net_name, net_type=net_type) - + def set_exclude_cell_names(self, cell_names: set): self.cell_names = cell_names - - def write_placement_back(self, - dm_inst_ptr, - node_x, - node_y): - self.ecc.write_placement_back(dm_inst_ptr, - node_x, - node_y) - + + def write_placement_back(self, dm_inst_ptr, node_x, node_y): + self.ecc.write_placement_back(dm_inst_ptr, node_x, node_y) + ######################################################################## # data io api ######################################################################## - def init_techlef(self, tech_lef_path : str): + def init_techlef(self, tech_lef_path: str): """init tech lef""" self.ecc.tech_lef_init(tech_lef_path) @@ -79,12 +68,9 @@ def read_def(self, path: str = ""): """init def""" self.ecc.def_init(def_path=path) - def read_verilog(self, - verilog : str, - top_module: str): + def read_verilog(self, verilog: str, top_module: str): """init verilog""" - self.ecc.verilog_init(verilog, - top_module) + self.ecc.verilog_init(verilog, top_module) def def_save(self, def_path: str): """save def file""" @@ -98,15 +84,12 @@ def tcl_save(self, output_path: str): """save tcl file""" self.ecc.tcl_save(output_path) - def verilog_save(self, - output_verilog, - cell_names: set = set()): + def verilog_save(self, output_verilog, cell_names: set | None = None): """verilog save""" - self.ecc.netlist_save( - netlist_path=output_verilog, - exclude_cell_names=cell_names - ) - + if cell_names is None: + cell_names = set() + self.ecc.netlist_save(netlist_path=output_verilog, exclude_cell_names=cell_names) + ######################################################################## # feature api ######################################################################## @@ -115,85 +98,74 @@ def feature_sammry(self, json_path: str): generate feature summary """ self.ecc.feature_summary(json_path) - - def feature_step(self, - step: str, - json_path: str): + + def feature_step(self, step: str, json_path: str): """ generate step feature """ self.ecc.feature_tool(json_path, step) - + ######################################################################## # reports api ######################################################################## - def report_summary(self, - path: str): + def report_summary(self, path: str): """ generate step report """ self.ecc.report_db(path) - + ######################################################################## # CTS api ######################################################################## - def run_cts(self, - config: str, - output : str) -> bool: + def run_cts(self, config: str, output: str) -> bool: return self.ecc.run_cts(config, output) - - def report_cts(self, output : str): + + def report_cts(self, output: str): self.ecc.cts_report(output) - - def feature_cts_map(self, - json_path: str, - map_grid_size=1): + + def feature_cts_map(self, json_path: str, map_grid_size=1): """ generate cts map feature """ self.ecc.feature_cts_eval(json_path, map_grid_size) - - ######################################################################## + + ######################################################################## # DRC api ######################################################################## - def init_drc(self, - output_dir : str, - therad_number : int = 128): + def init_drc(self, output_dir: str, therad_number: int = 128): """ init drc config """ - self.ecc.init_drc( - temp_directory_path=output_dir, - thread_number=therad_number) - - def run_drc(self, - config: str, - report_path : str="") -> bool: + self.ecc.init_drc(temp_directory_path=output_dir, thread_number=therad_number) + + def run_drc(self, config: str, report_path: str = "") -> bool: """ run drc check """ self.ecc.run_drc(config=config, report=report_path) - + def save_drc(self, feature_path: str): """ generate drc result """ self.ecc.save_drc(path=feature_path) - - ######################################################################## + + ######################################################################## # floorplan api ######################################################################## - def init_floorplan(self, - die_area: str, - core_area: str, - core_site: str, - io_site: str, - corner_site: str, - core_util: double, - x_margin: double, - y_margin: double, - aspect_ratio: double, - cell_area: double): + def init_floorplan( + self, + die_area: str, + core_area: str, + core_site: str, + io_site: str, + corner_site: str, + core_util: double, + x_margin: double, + y_margin: double, + aspect_ratio: double, + cell_area: double, + ): """ init floorplan Example: @@ -210,15 +182,12 @@ def init_floorplan(self, x_margin=x_margin, y_margin=y_margin, xy_ratio=aspect_ratio, - cell_area=cell_area) + cell_area=cell_area, + ) def init_floorplan_by_area( - self, - die_area: str, - core_area: str, - core_site: str, - io_site: str, - corner_site: str): + self, die_area: str, core_area: str, core_site: str, io_site: str, corner_site: str + ): """ init floorplan by die area and core area """ @@ -232,7 +201,8 @@ def init_floorplan_by_area( x_margin=0, y_margin=0, aspect_ratio=0, - cell_area=0) + cell_area=0, + ) def init_floorplan_by_core_utilization( self, @@ -243,7 +213,8 @@ def init_floorplan_by_core_utilization( x_margin: double, y_margin: double, aspect_ratio: double, - cell_area: double = 0): + cell_area: double = 0, + ): """ init floorplan by core utilization """ @@ -257,137 +228,115 @@ def init_floorplan_by_core_utilization( x_margin=x_margin, y_margin=y_margin, aspect_ratio=aspect_ratio, - cell_area=cell_area) + cell_area=cell_area, + ) - def gern_track(self, - layer: str, - x_start: int, - x_step: int, - y_start: int, - y_step: int): + def gern_track(self, layer: str, x_start: int, x_step: int, y_start: int, y_step: int): """ generate track """ return self.ecc.gern_track( - layer=layer, - x_start=x_start, - x_step=x_step, - y_start=y_start, - y_step=y_step) - - def add_pdn_io(self, - net_name: str, - direction: str, - is_power: bool, - pin_name: str = None): + layer=layer, x_start=x_start, x_step=x_step, y_start=y_start, y_step=y_step + ) + + def add_pdn_io(self, net_name: str, direction: str, is_power: bool, pin_name: str = None): if pin_name is None: pin_name = net_name - return self.ecc.add_pdn_io(pin_name=pin_name, - net_name=net_name, - direction=direction, - is_power=is_power) - - def global_net_connect(self, - net_name: str, - instance_pin_name: str, - is_power: bool): - return self.ecc.global_net_connect(net_name=net_name, - instance_pin_name=instance_pin_name, - is_power=is_power) - - def create_pdn_grid(self, - layer : str, - net_power : str, - net_ground : str, - width : double, - offset : double): - return self.ecc.create_grid(layer_name=layer, - net_name_power=net_power, - net_name_ground=net_ground, - width=width, - offset=offset) - - def create_pdn_stripe(self, - layer : str, - net_power : str, - net_ground : str, - width : double, - pitch : double, - offset : double): - return self.ecc.create_stripe(layer_name=layer, - net_name_power=net_power, - net_name_ground=net_ground, - width=width, - pitch=pitch, - offset=offset) - - def connect_pdn_layers(self, - layers : list[str]): + return self.ecc.add_pdn_io( + pin_name=pin_name, net_name=net_name, direction=direction, is_power=is_power + ) + + def global_net_connect(self, net_name: str, instance_pin_name: str, is_power: bool): + return self.ecc.global_net_connect( + net_name=net_name, instance_pin_name=instance_pin_name, is_power=is_power + ) + + def create_pdn_grid( + self, layer: str, net_power: str, net_ground: str, width: double, offset: double + ): + return self.ecc.create_grid( + layer_name=layer, + net_name_power=net_power, + net_name_ground=net_ground, + width=width, + offset=offset, + ) + + def create_pdn_stripe( + self, + layer: str, + net_power: str, + net_ground: str, + width: double, + pitch: double, + offset: double, + ): + return self.ecc.create_stripe( + layer_name=layer, + net_name_power=net_power, + net_name_ground=net_ground, + width=width, + pitch=pitch, + offset=offset, + ) + + def connect_pdn_layers(self, layers: list[str]): return self.ecc.connect_two_layer(layers=layers) - def auto_place_pins(self, - layer: str, - width: int, - height: int, - sides: list[str] = []): + def auto_place_pins( + self, + layer: str, + width: int, + height: int, + sides: list[str] | None = None, + ): """ layer : layer place io pins witdh : io pin width, in dbu height : io pin height, in dbu sides : "left", "rigth", "top", "bottom", if empty, place io pins around die. """ - return self.ecc.auto_place_pins( - layer=layer, - width=width, - height=height, - sides=sides - ) + if sides is None: + sides = [] + return self.ecc.auto_place_pins(layer=layer, width=width, height=height, sides=sides) + + def tapcell(self, tapcell: str, distance: double, endcap: str): + return self.ecc.tapcell(tapcell=tapcell, distance=distance, endcap=endcap) - def tapcell(self, - tapcell: str, - distance: double, - endcap: str): - return self.ecc.tapcell(tapcell=tapcell, - distance=distance, - endcap=endcap) - ######################################################################## # pdn api ######################################################################## def pnp(self, config: str): self.ecc.run_pnp(config) - + ######################################################################## # placement api ######################################################################## def run_placement(self, config: str): self.ecc.run_placer(config) - + def feature_placement_map(self, json_path: str, map_grid_size=1): """ generate placement map feature """ self.ecc.feature_pl_eval(json_path, map_grid_size) - + def run_legalize(self, config: str): self.ecc.run_incremental_lg() - + def run_filler(self, config: str): self.ecc.run_filler(config) - + def run_macro_placement(self, config: str, tcl_path=""): """ run macro placement """ self.ecc.runMP(config, tcl_path) - + def run_refinement(self, tcl_path=""): self.ecc.runRef(tcl_path) - - def run_ai_placement(self, - config: str, - onnx_path: str, - normalization_path: str): + + def run_ai_placement(self, config: str, onnx_path: str, normalization_path: str): """ Run AI-guided placement using ONNX model @@ -395,19 +344,14 @@ def run_ai_placement(self, onnx_path: Path to the ONNX model file normalization_path: Path to the normalization parameters JSON file """ - self.ecc.run_ai_placement(config, - onnx_path, - normalization_path) - - def feature_macro_drc_distribution(self, - path: str, - drc_path: str): + self.ecc.run_ai_placement(config, onnx_path, normalization_path) + + def feature_macro_drc_distribution(self, path: str, drc_path: str): """ build macro drc distribution """ - self.ecc.feature_macro_drc(path=path, - drc_path=drc_path) - + self.ecc.feature_macro_drc(path=path, drc_path=drc_path) + ######################################################################## # routing api ######################################################################## @@ -415,52 +359,52 @@ def run_routing(self, config: str): self.ecc.init_rt(config=config) self.ecc.run_rt() self.ecc.destroy_rt() - + def close_routing(self): self.ecc.destroy_rt() - + # read route json file to ecc route data def feature_route_read(self, json_path: str): self.ecc.feature_route_read(path=json_path) # read route def and save route data to json def feature_route(self, json_path: str): - self.ecc.feature_route(path=json_path) - - def is_rt_timing_enable(self, config : str): - import os + self.ecc.feature_route(path=json_path) + + def is_rt_timing_enable(self, config: str): import json + import os + if os.path.exists(config): - with open(config, "r", encoding="utf-8") as f_reader: + with open(config, encoding="utf-8") as f_reader: json_data = json.load(f_reader) # check if time enable - if json_data is not None and json_data.get("RT", {}).get("-enable_timing", "0") == "1": + if ( + json_data is not None + and json_data.get("RT", {}).get("-enable_timing", "0") == "1" + ): return True return False - + ######################################################################## # STA api ######################################################################## - def init_sta(self, - output_dir : str, - design : str, - lib_paths : list[str], - sdc_path: str): + def init_sta(self, output_dir: str, design: str, lib_paths: list[str], sdc_path: str): self.ecc.set_design_workspace(output_dir) self.ecc.read_liberty(lib_paths) self.ecc.link_design(design) self.ecc.read_sdc(sdc_path) - - def read_liberty(self, lib_paths : list[str]): + + def read_liberty(self, lib_paths: list[str]): self.ecc.read_liberty(lib_paths) - - def link_design(self, design : str): + + def link_design(self, design: str): self.ecc.link_design(design) - def read_sdc(self, sdc_path : str): + def read_sdc(self, sdc_path: str): self.ecc.read_sdc(sdc_path) - + def create_data_flow(self): self.ecc.create_data_flow() @@ -471,28 +415,31 @@ def get_used_libs(self): libs = self.ecc.get_used_libs() return libs + ######################################################################## # timing opt api ######################################################################## def run_timing_opt_drv(self, config: str): self.ecc.run_to_drv(config) - + def run_timing_opt_hold(self, config: str): self.ecc.run_to_hold(config) - + def run_timing_opt_setup(self, config: str): self.ecc.run_to_setup(config) - + ######################################################################## # data vectorization ######################################################################## - def generate_vectors(self, - vectors_dir : str, - patch_row_step: int = 9, - patch_col_step: int = 9, - batch_mode: bool = True, - is_placement_mode: bool = False, - sta_mode: int = 0): + def generate_vectors( + self, + vectors_dir: str, + patch_row_step: int = 9, + patch_col_step: int = 9, + batch_mode: bool = True, + is_placement_mode: bool = False, + sta_mode: int = 0, + ): """ generate vectorized data from design """ @@ -505,8 +452,7 @@ def generate_vectors(self, sta_mode=sta_mode, ) - - def vectors_nets_to_def(self, vectors_dir : str): + def vectors_nets_to_def(self, vectors_dir: str): """ save vectorized data to def """ @@ -514,9 +460,9 @@ def vectors_nets_to_def(self, vectors_dir : str): def vectors_nets_patterns_to_def(self, path): self.ecc.read_vectors_nets_patterns(path=path) - + ######################################################################## # net optimization ######################################################################## - def run_net_opt(self, config : str): - return self.ecc.run_no_fixfanout(config) \ No newline at end of file + def run_net_opt(self, config: str): + return self.ecc.run_no_fixfanout(config) diff --git a/chipcompiler/tools/ecc/plot.py b/chipcompiler/tools/ecc/plot.py index b2a9cc1e..63799af4 100644 --- a/chipcompiler/tools/ecc/plot.py +++ b/chipcompiler/tools/ecc/plot.py @@ -1,29 +1,25 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- +import concurrent.futures import os -import concurrent.futures from tqdm import tqdm -from chipcompiler.data import WorkspaceStep, Workspace, Parameters, StepEnum -from chipcompiler.utility import ( - json_read, - plot_csv_map, - plot_csv_table, - plot_metrics -) + +from chipcompiler.data import StepEnum, Workspace, WorkspaceStep +from chipcompiler.utility import json_read, plot_csv_map, plot_csv_table, plot_metrics + class ECCToolsPlot: def __init__(self, workspace: Workspace, step: WorkspaceStep): self.workspace = workspace self.step = step - + def plot(self) -> bool: state = True match self.step.name: case StepEnum.FLOORPLAN.value: state = state & self.plot_step_metrics() case StepEnum.NETLIST_OPT.value: - state = state & self.plot_step_metrics() + state = state & self.plot_step_metrics() case StepEnum.PLACEMENT.value: state = state & self.plot_step_metrics() & self.plot_placement_heatmap() case StepEnum.CTS.value: @@ -39,17 +35,17 @@ def plot(self) -> bool: case StepEnum.DRC.value: state = state & self.plot_step_metrics() & self.plot_drc_statis() case StepEnum.FILLER.value: - state = state & self.plot_step_metrics() - - case default: + state = state & self.plot_step_metrics() + + case _: self.workspace.logger.warning(f"Step {self.step.name} not supported for plotting.") - + self.workspace.logger.info(f"Plotting completed for step {self.step.name}") return state - + def plot_step_metrics(self) -> bool: # generate report image and dscription - json_path = self.step.analysis.get('metrics', "") + json_path = self.step.analysis.get("metrics", "") image_path = json_path.replace(".json", ".png") metrics = json_read(json_path) return plot_metrics(metrics=metrics, output_path=image_path) @@ -59,146 +55,167 @@ def plot_placement_heatmap(self) -> bool: json_map = json_read(json_map_path) if not json_map: return False - + # density map csv_list = [] - csv_list.extend([ - json_map.get("Density", {}).get("cell", {}).get("allcell_density", ""), - json_map.get("Density", {}).get("cell", {}).get("macro_density", ""), - json_map.get("Density", {}).get("cell", {}).get("stdcell_density", ""), - json_map.get("Density", {}).get("margin", {}).get("horizontal", ""), - json_map.get("Density", {}).get("margin", {}).get("union", ""), - json_map.get("Density", {}).get("margin", {}).get("vertical", ""), - json_map.get("Density", {}).get("net", {}).get("allnet_density", ""), - json_map.get("Density", {}).get("net", {}).get("global_net_density", ""), - json_map.get("Density", {}).get("net", {}).get("local_net_density", ""), - json_map.get("Density", {}).get("pin", {}).get("allcell_pin_density", ""), - json_map.get("Density", {}).get("pin", {}).get("macro_pin_density", ""), - json_map.get("Density", {}).get("pin", {}).get("stdcell_pin_density", ""), - json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("horizontal", ""), - json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("union", ""), - json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("vertical", ""), - json_map.get("Congestion", {}).get("map", {}).get("lutrudy", {}).get("horizontal", ""), - json_map.get("Congestion", {}).get("map", {}).get("lutrudy", {}).get("union", ""), - json_map.get("Congestion", {}).get("map", {}).get("lutrudy", {}).get("vertical", ""), - json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("horizontal", ""), - json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("union", ""), - json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("vertical", "") - ]) + csv_list.extend( + [ + json_map.get("Density", {}).get("cell", {}).get("allcell_density", ""), + json_map.get("Density", {}).get("cell", {}).get("macro_density", ""), + json_map.get("Density", {}).get("cell", {}).get("stdcell_density", ""), + json_map.get("Density", {}).get("margin", {}).get("horizontal", ""), + json_map.get("Density", {}).get("margin", {}).get("union", ""), + json_map.get("Density", {}).get("margin", {}).get("vertical", ""), + json_map.get("Density", {}).get("net", {}).get("allnet_density", ""), + json_map.get("Density", {}).get("net", {}).get("global_net_density", ""), + json_map.get("Density", {}).get("net", {}).get("local_net_density", ""), + json_map.get("Density", {}).get("pin", {}).get("allcell_pin_density", ""), + json_map.get("Density", {}).get("pin", {}).get("macro_pin_density", ""), + json_map.get("Density", {}).get("pin", {}).get("stdcell_pin_density", ""), + json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("horizontal", ""), + json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("union", ""), + json_map.get("Congestion", {}).get("map", {}).get("egr", {}).get("vertical", ""), + json_map.get("Congestion", {}) + .get("map", {}) + .get("lutrudy", {}) + .get("horizontal", ""), + json_map.get("Congestion", {}).get("map", {}).get("lutrudy", {}).get("union", ""), + json_map.get("Congestion", {}) + .get("map", {}) + .get("lutrudy", {}) + .get("vertical", ""), + json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("horizontal", ""), + json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("union", ""), + json_map.get("Congestion", {}).get("map", {}).get("rudy", {}).get("vertical", ""), + ] + ) self.plot_array_maps(input_paths=csv_list) - + return True - + def plot_routing_heatmap(self) -> bool: data_dir = self.step.data.get(f"{StepEnum.ROUTING.value}", "") if not os.path.exists(data_dir): return False - + csv_list = [] - for root, dirs, files in os.walk(data_dir): + for root, _dirs, files in os.walk(data_dir): for file in files: if file.endswith(".csv"): csv_path = os.path.join(root, file) csv_list.append(csv_path) - + self.plot_array_maps(input_paths=csv_list) - + return True - - def plot_array_maps(self, input_paths : list[str]): + + def plot_array_maps(self, input_paths: list[str]): """ Plot array maps from multiple CSV files using multi-threading with progress bar. - + Args: input_paths (list[str]): List of paths to input CSV files. """ if not input_paths: return - + # Filter out invalid paths - valid_paths = [path for path in input_paths if path and os.path.exists(path) and path.lower().endswith(".csv")] - + valid_paths = [ + path + for path in input_paths + if path and os.path.exists(path) and path.lower().endswith(".csv") + ] + if not valid_paths: self.workspace.logger.warning("No valid CSV files found for plotting.") return - - self.workspace.logger.info(f"Plotting {len(valid_paths)} array maps with multi-threading...") - + + self.workspace.logger.info( + f"Plotting {len(valid_paths)} array maps with multi-threading..." + ) + # Use ThreadPoolExecutor for multi-threading with progress bar (limit to 10 threads) with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: # Create a progress bar - results = list(tqdm( - executor.map(plot_csv_map, valid_paths), - total=len(valid_paths), - desc="Plotting array maps", - unit="file" - )) - + results = list( + tqdm( + executor.map(plot_csv_map, valid_paths), + total=len(valid_paths), + desc="Plotting array maps", + unit="file", + ) + ) + # Count successful and failed plots successful = sum(results) failed = len(results) - successful - + self.workspace.logger.info(f"Plotting completed: {successful} successful, {failed} failed.") - + def plot_drc_statis(self) -> bool: # build layer header layers = [] - layer_dict = {} # layer drc number distribution + layer_dict = {} # layer drc number distribution db_json = json_read(self.step.feature.get("db", "")) - + # Get cut layers for item in db_json.get("Layers", {}).get("cut_layers", []): layer_dict[item.get("layer_name")] = 0 layers.append(item) - + # Get routing layers for item in db_json.get("Layers", {}).get("routing_layers", []): layer_dict[item.get("layer_name")] = 0 layers.append(item) - + # Sort layers by layer_order def cmp_layer(item): - return item.get("layer_order", 0) + return item.get("layer_order", 0) + sorted_layers = sorted(layers, key=cmp_layer) - + # Get layer names in order layer_names = [layer.get("layer_name") for layer in sorted_layers] layer_names.append("total") - + # build drc statis drc_json = json_read(self.step.feature.get("step", "")) if len(drc_json) == 0: return False - + # Get DRC distribution data drc_distribution = drc_json.get("drc", {}).get("distribution", {}) - + # Build drc_statis dictionary drc_statis = {} import copy - drc_total_dict= copy.deepcopy(layer_dict) + + drc_total_dict = copy.deepcopy(layer_dict) for drc_type, drc_data in drc_distribution.items(): drc_statis[drc_type] = copy.deepcopy(layer_dict) - + for layer, layer_data in drc_data.get("layers", {}).items(): - drc_statis[drc_type][layer] = drc_statis[drc_type][layer] + layer_data.get("number", 0) - drc_total_dict[layer] = drc_total_dict.get(layer, 0) + + layer_data.get("number", 0) - + drc_statis[drc_type][layer] = drc_statis[drc_type][layer] + layer_data.get( + "number", 0 + ) + drc_total_dict[layer] = drc_total_dict.get(layer, 0) + +layer_data.get("number", 0) + drc_total_dict["total"] = drc_json.get("drc", {}).get("number", 0) drc_statis["total"] = drc_total_dict - + # Save drc_statis to CSV file import csv + statis_csv = self.step.analysis.get("statis_csv", "") # Write to CSV file - with open(statis_csv, 'w', newline='') as csvfile: + with open(statis_csv, "w", newline="") as csvfile: # Define headers: first column is "Type", followed by layer names csv_headers = ["Type"] + layer_names writer = csv.DictWriter(csvfile, fieldnames=csv_headers) - + # Write headers writer.writeheader() - + # Write data rows for drc_type, layer_counts in drc_statis.items(): row = {"Type": drc_type} @@ -206,11 +223,11 @@ def cmp_layer(item): for layer in layer_names: row[layer] = layer_counts.get(layer, 0) writer.writerow(row) - + # Log the CSV creation self.workspace.logger.info(f"DRC statistics saved to {statis_csv}") - + # Plot the CSV table plot_csv_table(input_path=statis_csv) - + return True diff --git a/chipcompiler/tools/ecc/runner.py b/chipcompiler/tools/ecc/runner.py index b3b2d99a..00a70da6 100644 --- a/chipcompiler/tools/ecc/runner.py +++ b/chipcompiler/tools/ecc/runner.py @@ -1,49 +1,51 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys import os - -from chipcompiler.data import WorkspaceStep, Workspace, StateEnum, StepEnum + +from chipcompiler.data import StateEnum, StepEnum, Workspace, WorkspaceStep +from chipcompiler.tools.ecc.checklist import EccChecklist +from chipcompiler.tools.ecc.metrics import build_step_metrics from chipcompiler.tools.ecc.module import ECCToolsModule -from chipcompiler.tools.ecc.utility import is_eda_exist from chipcompiler.tools.ecc.plot import ECCToolsPlot -from chipcompiler.tools.ecc.metrics import build_step_metrics from chipcompiler.tools.ecc.subflow import EccSubFlow -from chipcompiler.tools.ecc.checklist import EccChecklist +from chipcompiler.tools.ecc.utility import is_eda_exist -def create_db_engine(workspace: Workspace, - step: WorkspaceStep) -> ECCToolsModule: + +def create_db_engine(workspace: Workspace, step: WorkspaceStep) -> ECCToolsModule: """""" if not is_eda_exist(): return False eda_inst = ECCToolsModule() - - eda_inst.init_config(flow_config=step.config["flow"], - db_config=step.config["db"], - output_dir=step.data["dir"], - feature_dir=step.feature["dir"]) - + + eda_inst.init_config( + flow_config=step.config["flow"], + db_config=step.config["db"], + output_dir=step.data["dir"], + feature_dir=step.feature["dir"], + ) + eda_inst.init_techlef(workspace.pdk.tech) eda_inst.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"]) + eda_inst.read_def(step.input["def"]) else: - #else, read step output verilog + # else, read step output verilog if os.path.exists(step.input["verilog"]): - eda_inst.read_verilog(verilog=step.input["verilog"], - top_module=workspace.design.top_module) + eda_inst.read_verilog( + verilog=step.input["verilog"], top_module=workspace.design.top_module + ) else: return None - + return eda_inst -def get_eda_instance(workspace: Workspace, - step: WorkspaceStep, - instance: ECCToolsModule=None) -> ECCToolsModule: + +def get_eda_instance( + workspace: Workspace, step: WorkspaceStep, instance: ECCToolsModule = None +) -> ECCToolsModule: """ - module is ecc module from db engine, + module is ecc module from db engine, eda instacnce may initialize data from this module if module has been set """ eda_inst = None @@ -53,399 +55,344 @@ def get_eda_instance(workspace: Workspace, eda_inst = instance else: # init ecc module - eda_inst = create_db_engine(workspace=workspace, - step=step) - + eda_inst = create_db_engine(workspace=workspace, step=step) + return eda_inst -def save_data(step: WorkspaceStep, - module : ECCToolsModule) -> bool: + +def save_data(step: WorkspaceStep, module: ECCToolsModule) -> bool: """ - module is ecc module from db engine, + module is ecc module from db engine, eda instacnce may initialize data from this module if module has been set """ if module is None: - return FALSE - + return False + module.def_save(def_path=step.output["def"]) module.verilog_save(output_verilog=step.output["verilog"]) module.gds_save(output_path=step.output["gds"]) module.feature_sammry(json_path=step.feature["db"]) - module.feature_step(step=step.name, - json_path=step.feature["step"]) - + module.feature_step(step=step.name, json_path=step.feature["step"]) + module.report_summary(path=step.report["db"]) - + return True - -def run_step(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + + +def run_step(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: if not is_eda_exist(): return StateEnum.Invalid - + state = False - match(step.name): + match step.name: case StepEnum.FLOORPLAN.value: - state = run_floorplan(workspace=workspace, - step=step, - module=module) + state = run_floorplan(workspace=workspace, step=step, module=module) case StepEnum.NETLIST_OPT.value: - state = run_net_opt(workspace=workspace, - step=step, - module=module) + state = run_net_opt(workspace=workspace, step=step, module=module) case StepEnum.PLACEMENT.value: - state = run_placement(workspace=workspace, - step=step, - module=module) + state = run_placement(workspace=workspace, step=step, module=module) case StepEnum.CTS.value: - state = run_cts(workspace=workspace, - step=step, - module=module) + state = run_cts(workspace=workspace, step=step, module=module) case StepEnum.TIMING_OPT_DRV.value: - state = run_timing_opt_drv(workspace=workspace, - step=step, - module=module) + state = run_timing_opt_drv(workspace=workspace, step=step, module=module) case StepEnum.TIMING_OPT_HOLD.value: - state = run_timing_opt_hold(workspace=workspace, - step=step, - module=module) + state = run_timing_opt_hold(workspace=workspace, step=step, module=module) case StepEnum.LEGALIZATION.value: - state = run_legalization(workspace=workspace, - step=step, - module=module) + state = run_legalization(workspace=workspace, step=step, module=module) case StepEnum.ROUTING.value: - state = run_routing(workspace=workspace, - step=step, - module=module) + state = run_routing(workspace=workspace, step=step, module=module) case StepEnum.DRC.value: - state = run_drc(workspace=workspace, - step=step, - module=module) + state = run_drc(workspace=workspace, step=step, module=module) case StepEnum.FILLER.value: - state = run_filler(workspace=workspace, - step=step, - module=module) - + state = run_filler(workspace=workspace, step=step, module=module) + if state: # save metrics - build_step_metrics(workspace=workspace, - step=step) - + build_step_metrics(workspace=workspace, step=step) + # plot layout image - ploter = ECCToolsPlot(workspace=workspace, - step=step) - ploter.plot() - - # do checklist + ploter = ECCToolsPlot(workspace=workspace, step=step) + ploter.plot() + + # do checklist checklist = EccChecklist(workspace=workspace, workspace_step=step) checklist.check() - + return state -def run_net_opt(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_net_opt(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run net optimization """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_net_opt(config=step.config[f"{StepEnum.NETLIST_OPT.value}"]) - + sub_flow.update_step(step_name="run net optimization", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut - -def run_placement(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + + +def run_placement(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run placement """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_placement(config=step.config[f"{StepEnum.PLACEMENT.value}"]) eda_inst.feature_placement_map(json_path=step.feature["map"]) - + sub_flow.update_step(step_name="run placement", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_cts(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_cts(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run CTS """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - - eda_inst.run_cts(config=step.config[f"{StepEnum.CTS.value}"], - output=step.data[f"{StepEnum.CTS.value}"]) - + + eda_inst.run_cts( + config=step.config[f"{StepEnum.CTS.value}"], output=step.data[f"{StepEnum.CTS.value}"] + ) + eda_inst.report_cts(output=step.data[f"{StepEnum.CTS.value}"]) - + eda_inst.run_legalize(config=step.config[f"{StepEnum.LEGALIZATION.value}"]) - + eda_inst.feature_cts_map(json_path=step.feature["map"]) - + sub_flow.update_step(step_name="run CTS", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_timing_opt_drv(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_timing_opt_drv( + workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None +) -> bool: """ run timing optization drv """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_timing_opt_drv(config=step.config[f"{StepEnum.TIMING_OPT_DRV.value}"]) - + sub_flow.update_step(step_name="run timing opt drv", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_timing_opt_hold(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_timing_opt_hold( + workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None +) -> bool: """ - run timing optization hold + run timing optization hold """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_timing_opt_hold(config=step.config[f"{StepEnum.TIMING_OPT_HOLD.value}"]) - + sub_flow.update_step(step_name="run timing opt hold", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_routing(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_routing(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run routing """ reslut = False - + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - - + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + if eda_inst.is_rt_timing_enable(config=step.config[f"{StepEnum.ROUTING.value}"]): - eda_inst.init_sta(output_dir=step.data["sta"], - design=workspace.design.name, - lib_paths=workspace.pdk.libs, - sdc_path=workspace.pdk.sdc) - + eda_inst.init_sta( + output_dir=step.data["sta"], + design=workspace.design.name, + lib_paths=workspace.pdk.libs, + sdc_path=workspace.pdk.sdc, + ) + eda_inst.run_routing(config=step.config[f"{StepEnum.ROUTING.value}"]) - + sub_flow.update_step(step_name="run routing", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_drc(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: +def run_drc(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run chip drc """ reslut = False - - sub_flow = EccSubFlow(workspace=workspace, - workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - - + + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", 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}"], - report_path=step.report["step"]) - + eda_inst.run_drc( + config=step.config[f"{StepEnum.DRC.value}"], report_path=step.report["step"] + ) + sub_flow.update_step(step_name="run DRC", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - - eda_inst.save_drc(feature_path=step.feature[f"step"]) - + + eda_inst.save_drc(feature_path=step.feature["step"]) + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_legalization(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_legalization( + workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None +) -> bool: """ run placement legalization """ reslut = False - - sub_flow = EccSubFlow(workspace=workspace, - workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_legalize(config=step.config[f"{StepEnum.LEGALIZATION.value}"]) - + sub_flow.update_step(step_name="run routing", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_filler(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_filler(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run placement filler """ reslut = False - - sub_flow = EccSubFlow(workspace=workspace, - workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: sub_flow.update_step(step_name="load data", state=StateEnum.Success) - + eda_inst.run_filler(config=step.config[f"{StepEnum.FILLER.value}"]) - + sub_flow.update_step(step_name="run filler", state=StateEnum.Success) - + reslut = save_data(step=step, module=eda_inst) - + sub_flow.update_step(step_name="save data", state=StateEnum.Success) - + return reslut -def run_floorplan(workspace: Workspace, - step: WorkspaceStep, - module : ECCToolsModule = None) -> bool: + +def run_floorplan(workspace: Workspace, step: WorkspaceStep, module: ECCToolsModule = None) -> bool: """ run floorplan """ - - sub_flow = EccSubFlow(workspace=workspace, - workspace_step=step) - - eda_inst = get_eda_instance(workspace=workspace, - step=step, - instance=module) - + + sub_flow = EccSubFlow(workspace=workspace, workspace_step=step) + + eda_inst = get_eda_instance(workspace=workspace, step=step, instance=module) + if eda_inst is not None: - sub_flow.update_step(step_name="load data", - state=StateEnum.Success) - + sub_flow.update_step(step_name="load data", 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) eda_inst.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, - ) - + 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", "") @@ -454,62 +401,62 @@ def run_floorplan(workspace: Workspace, # core_site=workspace.pdk.site_core, # io_site=workspace.pdk.site_io, # corner_site=workspace.pdk.site_corner) - - sub_flow.update_step(step_name="init floorplan", - state=StateEnum.Success) - + + sub_flow.update_step(step_name="init floorplan", state=StateEnum.Success) + json_floorplan = workspace.parameters.data.get("Floorplan", {}) - + # create tracks json_track = json_floorplan.get("Tracks", []) for item in json_track: - eda_inst.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), - y_step=item.get("y step", 0)) - sub_flow.update_step(step_name="create tracks", - state=StateEnum.Success) - + eda_inst.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), + y_step=item.get("y step", 0), + ) + sub_flow.update_step(step_name="create tracks", state=StateEnum.Success) + # PDN json_PDN = workspace.parameters.data.get("PDN", {}) - + # IO placement json_io_pins = json_PDN.get("IO", {}) for item in json_io_pins: 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, - direction=direction, - is_power=is_power) - + eda_inst.add_pdn_io(net_name=net_name, direction=direction, is_power=is_power) + # PDN global connect json_global_connect = json_PDN.get("Global connect", {}) for item in json_global_connect: 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, - instance_pin_name=instance_pin_name, - is_power=is_power) - + eda_inst.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", ""), - 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="place io pins", - state=StateEnum.Success) - + eda_inst.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="place io pins", state=StateEnum.Success) + # tap cell - eda_inst.tapcell(tapcell=workspace.pdk.tap_cell, - distance=json_floorplan.get("Tap distance", 0), - endcap=workspace.pdk.end_cap) - sub_flow.update_step(step_name="tap cell", - state=StateEnum.Success) - + eda_inst.tapcell( + tapcell=workspace.pdk.tap_cell, + distance=json_floorplan.get("Tap distance", 0), + endcap=workspace.pdk.end_cap, + ) + sub_flow.update_step(step_name="tap cell", state=StateEnum.Success) + # PDN grid json_pdn_grid = json_PDN.get("Grid", {}) if len(json_pdn_grid) > 0: @@ -518,12 +465,10 @@ 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, - net_power=power_net, - net_ground=ground_net, - width=width, - offset=offset) - + eda_inst.create_pdn_grid( + layer=layer, net_power=power_net, net_ground=ground_net, width=width, offset=offset + ) + # PDN stripe # json_pdn_stripe = json_PDN.get("Stripe", {}) # for item in json_pdn_stripe: @@ -539,35 +484,31 @@ def run_floorplan(workspace: Workspace, # width=width, # pitch=pitch, # offset=offset) - + # # PDN connect layers # json_pdn_connect_layers= json_PDN.get("Connect layers", []) # for item in json_pdn_connect_layers: # layers = item.get("layers", []) # if len(layers) >= 2: # eda_inst.connect_pdn_layers(layers) - - sub_flow.update_step(step_name="PDN", - state=StateEnum.Success) - + + sub_flow.update_step(step_name="PDN", state=StateEnum.Success) + # set clock net clock_name = workspace.parameters.data.get("Clock", "") - eda_inst.set_net(net_name=clock_name, - net_type="CLOCK") - sub_flow.update_step(step_name="set clock net", - state=StateEnum.Success) - + eda_inst.set_net(net_name=clock_name, net_type="CLOCK") + sub_flow.update_step(step_name="set clock net", state=StateEnum.Success) + # return save_data(step=step, module=eda_inst) - + eda_inst.def_save(def_path=step.output["def"]) eda_inst.verilog_save(output_verilog=step.output["verilog"]) eda_inst.gds_save(output_path=step.output["gds"]) eda_inst.feature_sammry(json_path=step.feature["db"]) - - eda_inst.report_summary(path=step.report["db"]) - - sub_flow.update_step(step_name="save data", - state=StateEnum.Success) + + eda_inst.report_summary(path=step.report["db"]) + + sub_flow.update_step(step_name="save data", state=StateEnum.Success) return True - - return False \ No newline at end of file + + return False diff --git a/chipcompiler/tools/ecc/service.py b/chipcompiler/tools/ecc/service.py index 57c03c9d..9d957048 100644 --- a/chipcompiler/tools/ecc/service.py +++ b/chipcompiler/tools/ecc/service.py @@ -1,23 +1,15 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import ( - Workspace, - WorkspaceStep, - StepEnum -) - +from chipcompiler.data import StepEnum, Workspace, WorkspaceStep from chipcompiler.tools.ecc.metrics import build_step_metrics - from chipcompiler.utility import json_read - -def get_step_info(workspace: Workspace, - step: WorkspaceStep, - id : str) -> dict: + + +def get_step_info(workspace: Workspace, step: WorkspaceStep, id: str) -> dict: """ get step info by step and command id, return dict as resource definition """ step_info = {} - + match id: case "views": step_info = build_views(workspace=workspace, step=step) @@ -36,61 +28,49 @@ def get_step_info(workspace: Workspace, return step_info -def build_views(workspace: Workspace, - step: WorkspaceStep) -> dict: - metrics = build_step_metrics(workspace=workspace, - step=step) - - info = { - "image" : step.output.get("image", ""), - "metrics" : metrics.path, - "information" : {} - } - + +def build_views(workspace: Workspace, step: WorkspaceStep) -> dict: + metrics = build_step_metrics(workspace=workspace, step=step) + + info = {"image": step.output.get("image", ""), "metrics": metrics.path, "information": {}} + return info -def build_metrics(workspace: Workspace, - step: WorkspaceStep) -> dict: - metrics = build_step_metrics(workspace=workspace, - step=step) - info = { - "metrics" : metrics.path - } - + +def build_metrics(workspace: Workspace, step: WorkspaceStep) -> dict: + metrics = build_step_metrics(workspace=workspace, step=step) + info = {"metrics": metrics.path} + return info -def build_layout(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "image" : step.output.get("image", "") - } - + +def build_layout(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"image": step.output.get("image", "")} + return info -def build_subflow(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "path" : step.subflow.get("path", "") - } - + +def build_subflow(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"path": step.subflow.get("path", "")} + return info -def build_analysis(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_analysis(workspace: Workspace, step: WorkspaceStep) -> dict: info = { - "metrics" : step.analysis.get('metrics', ""), - "statis" : step.analysis.get('statis_csv', ""), - "data summary" : step.feature.get('db', ""), - "step feature" : step.feature.get('step', ""), - "step report" : step.report.get('db', "") + "metrics": step.analysis.get("metrics", ""), + "statis": step.analysis.get("statis_csv", ""), + "data summary": step.feature.get("db", ""), + "step feature": step.feature.get("step", ""), + "step report": step.report.get("db", ""), } - + return info -def build_maps(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_maps(workspace: Workspace, step: WorkspaceStep) -> dict: info = {} - + match StepEnum(step.name): case StepEnum.FLOORPLAN: pass @@ -114,146 +94,132 @@ def build_maps(workspace: Workspace, pass case StepEnum.FILLER: pass - + return info -def build_maps_congestion(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_maps_congestion(workspace: Workspace, step: WorkspaceStep) -> dict: info = {} - + json_data = json_read(step.feature.get("map", "")) if len(json_data) > 0: json_cong = json_data.get("Congestion", {}) json_map = json_cong.get("map", {}) - json_overflow = json_cong.get("overflow", {}) json_util = json_cong.get("utilization", {}) - + + lutrudy_util = json_util.get("lutrudy", {}) + rudy_util = json_util.get("rudy", {}) + # egr maps - info["egr-horizontal" ] = { - "path" : json_map.get("egr", {}).get("horizontal", ""), - "info" : [ - "" - ] - } - - info["egr-vertical" ] = { - "path" : json_map.get("egr", {}).get("vertical", ""), - "info" : [ - "" - ] + info["egr-horizontal"] = { + "path": json_map.get("egr", {}).get("horizontal", ""), + "info": [""], } - - info["egr-union" ] = { - "path" : json_map.get("egr", {}).get("union", ""), - "info" : [ - "" - ] - } - + + info["egr-vertical"] = {"path": json_map.get("egr", {}).get("vertical", ""), "info": [""]} + + info["egr-union"] = {"path": json_map.get("egr", {}).get("union", ""), "info": [""]} + # lut rudy map - info["lutrudy-horizontal" ] = { - "path" : json_map.get("lutrudy", {}).get("horizontal", ""), - "info" : [ - f"max utilization : {json_util.get('lutrudy', {}).get('max', {}).get('horizontal', 0)}", - f"top average : {json_util.get('lutrudy', {}).get('top_average', {}).get('horizontal', 0)}" - ] + info["lutrudy-horizontal"] = { + "path": json_map.get("lutrudy", {}).get("horizontal", ""), + "info": [ + f"max utilization : {lutrudy_util.get('max', {}).get('horizontal', 0)}", + f"top average : {lutrudy_util.get('top_average', {}).get('horizontal', 0)}", + ], } - - info["lutrudy-vertical" ] = { - "path" : json_map.get("lutrudy", {}).get("vertical", ""), - "info" : [ - f"max utilization : {json_util.get('lutrudy', {}).get('max', {}).get('vertical', 0)}", - f"top average : {json_util.get('lutrudy', {}).get('top_average', {}).get('vertical', 0)}" - ] + + info["lutrudy-vertical"] = { + "path": json_map.get("lutrudy", {}).get("vertical", ""), + "info": [ + f"max utilization : {lutrudy_util.get('max', {}).get('vertical', 0)}", + f"top average : {lutrudy_util.get('top_average', {}).get('vertical', 0)}", + ], } - - info["lutrudy-union" ] = { - "path" : json_map.get("lutrudy", {}).get("union", ""), - "info" : [ - f"max utilization : {json_util.get('lutrudy', {}).get('max', {}).get('union', 0)}", - f"top average : {json_util.get('lutrudy', {}).get('top_average', {}).get('union', 0)}" - ] + + info["lutrudy-union"] = { + "path": json_map.get("lutrudy", {}).get("union", ""), + "info": [ + f"max utilization : {lutrudy_util.get('max', {}).get('union', 0)}", + f"top average : {lutrudy_util.get('top_average', {}).get('union', 0)}", + ], } - + # rudy map - info["rudy-horizontal" ] = { - "path" : json_map.get("rudy", {}).get("horizontal", ""), - "info" : [ - f"max utilization : {json_util.get('rudy', {}).get('max', {}).get('horizontal', 0)}", - f"top average : {json_util.get('rudy', {}).get('top_average', {}).get('horizontal', 0)}" - ] + info["rudy-horizontal"] = { + "path": json_map.get("rudy", {}).get("horizontal", ""), + "info": [ + f"max utilization : {rudy_util.get('max', {}).get('horizontal', 0)}", + f"top average : {rudy_util.get('top_average', {}).get('horizontal', 0)}", + ], } - - info["rudy-vertical" ] = { - "path" : json_map.get("rudy", {}).get("vertical", ""), - "info" : [ - f"max utilization : {json_util.get('rudy', {}).get('max', {}).get('vertical', 0)}", - f"top average : {json_util.get('rudy', {}).get('top_average', {}).get('vertical', 0)}" - ] + + info["rudy-vertical"] = { + "path": json_map.get("rudy", {}).get("vertical", ""), + "info": [ + f"max utilization : {rudy_util.get('max', {}).get('vertical', 0)}", + f"top average : {rudy_util.get('top_average', {}).get('vertical', 0)}", + ], } - - info["rudy-union" ] = { - "path" : json_map.get("rudy", {}).get("union", ""), - "info" : [ - f"max utilization : {json_util.get('rudy', {}).get('max', {}).get('union', 0)}", - f"top average : {json_util.get('rudy', {}).get('top_average', {}).get('union', 0)}" - ] + + info["rudy-union"] = { + "path": json_map.get("rudy", {}).get("union", ""), + "info": [ + f"max utilization : {rudy_util.get('max', {}).get('union', 0)}", + f"top average : {rudy_util.get('top_average', {}).get('union', 0)}", + ], } - - + return info -def build_maps_density(workspace: Workspace, - step: WorkspaceStep) -> dict: +def build_maps_density(workspace: Workspace, step: WorkspaceStep) -> dict: info = {} - + json_data = json_read(step.feature.get("map", "")) if len(json_data) > 0: json_density = json_data.get("Density", {}) - + # cell - info["cell density" ] = { - "path" : json_density.get("cell", {}).get("allcell_density", ""), - "info" : [] + info["cell density"] = { + "path": json_density.get("cell", {}).get("allcell_density", ""), + "info": [], } - - info["macro density" ] = { - "path" : json_density.get("cell", {}).get("macro_density", ""), - "info" : [] + + info["macro density"] = { + "path": json_density.get("cell", {}).get("macro_density", ""), + "info": [], } - - info["stdcell density" ] = { - "path" : json_density.get("cell", {}).get("stdcell_density", ""), - "info" : [] + + info["stdcell density"] = { + "path": json_density.get("cell", {}).get("stdcell_density", ""), + "info": [], } - - info["net density" ] = { - "path" : json_density.get("net", {}).get("allnet_density", ""), - "info" : [] + + info["net density"] = { + "path": json_density.get("net", {}).get("allnet_density", ""), + "info": [], } - - info["global net density" ] = { - "path" : json_density.get("net", {}).get("global_net_density", ""), - "info" : [] + + info["global net density"] = { + "path": json_density.get("net", {}).get("global_net_density", ""), + "info": [], } - - info["local net density" ] = { - "path" : json_density.get("net", {}).get("local_net_density", ""), - "info" : [] + + info["local net density"] = { + "path": json_density.get("net", {}).get("local_net_density", ""), + "info": [], } - - info["pin density" ] = { - "path" : json_density.get("pin", {}).get("allcell_pin_density", ""), - "info" : [] + + info["pin density"] = { + "path": json_density.get("pin", {}).get("allcell_pin_density", ""), + "info": [], } - + return info -def build_checklist(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "path" : step.checklist.get("path", "") - } - - return info \ No newline at end of file + +def build_checklist(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"path": step.checklist.get("path", "")} + + return info diff --git a/chipcompiler/tools/ecc/subflow.py b/chipcompiler/tools/ecc/subflow.py index 889fbd9b..5d052871 100644 --- a/chipcompiler/tools/ecc/subflow.py +++ b/chipcompiler/tools/ecc/subflow.py @@ -1,16 +1,17 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StateEnum, StepEnum +from chipcompiler.data import StateEnum, StepEnum, Workspace, WorkspaceStep + class EccSubFlow: - def __init__(self, workspace : Workspace, workspace_step: WorkspaceStep): + def __init__(self, workspace: Workspace, workspace_step: WorkspaceStep): self.workspace = workspace self.workspace_step = workspace_step - + self.init_sub_flow() - + def init_sub_flow(self): from chipcompiler.utility import json_read + data = json_read(self.workspace_step.subflow.get("path", "")) if len(data) > 0: self.workspace_step.subflow["steps"] = data.get("steps", []) @@ -18,17 +19,17 @@ def init_sub_flow(self): self.build_sub_flow() def build_sub_flow(self) -> list: - def subflow_template(step_name : str): + def subflow_template(step_name: str): return { - "name" : step_name, # step name - "state" : StateEnum.Unstart.value, # step state - "runtime" : "", # step run time - "peak memory (mb)" : 0, # step peak memory - "info" : {} # step additional infomation - } - + "name": step_name, # step name + "state": StateEnum.Unstart.value, # step state + "runtime": "", # step run time + "peak memory (mb)": 0, # step peak memory + "info": {}, # step additional infomation + } + steps = [] - + step = StepEnum(self.workspace_step.name) match step: case StepEnum.FLOORPLAN: @@ -91,30 +92,36 @@ def subflow_template(step_name : str): steps.append(subflow_template("run DRC")) steps.append(subflow_template("save data")) # steps.append(subflow_template("analysis")) - + self.workspace_step.subflow["steps"] = steps - + self.save() - + def save(self) -> bool: from chipcompiler.utility import json_write - - return json_write(file_path=self.workspace_step.subflow.get("path", ""), - data=self.workspace_step.subflow) - - def update_step(self, - step_name : str, - state : str | StateEnum, - runtime : str = "", - memory : float = 0, - info : dict = {}): + + return json_write( + file_path=self.workspace_step.subflow.get("path", ""), data=self.workspace_step.subflow + ) + + def update_step( + self, + step_name: str, + state: str | StateEnum, + runtime: str = "", + memory: float = 0, + info: dict | None = None, + ): + if info is None: + info = {} + state = state.value if isinstance(state, StateEnum) else state - + for step_dict in self.workspace_step.subflow.get("steps", []): if step_dict.get("name") == step_name: step_dict["state"] = state step_dict["runtime"] = runtime step_dict["peak memory (mb)"] = memory step_dict["info"] = info - - self.save() \ No newline at end of file + + self.save() diff --git a/chipcompiler/tools/ecc/utility.py b/chipcompiler/tools/ecc/utility.py index dabe3ad6..48f0c6ab 100644 --- a/chipcompiler/tools/ecc/utility.py +++ b/chipcompiler/tools/ecc/utility.py @@ -1,10 +1,10 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- + def is_eda_exist() -> bool: """ Check if the ECC tool is installed and accessible. - """ + """ try: # from chipcompiler.tools.ecc.bin import ecc_py return True diff --git a/chipcompiler/tools/eda.py b/chipcompiler/tools/eda.py index 9b32485a..1a8c2152 100644 --- a/chipcompiler/tools/eda.py +++ b/chipcompiler/tools/eda.py @@ -1,75 +1,72 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StepMetrics import logging +from chipcompiler.data import StepMetrics, Workspace, WorkspaceStep + + def load_eda_module(eda_tool: str): """ Load and return the EDA tool module based on the given eda tool name. """ + def check_module(eda_module): - functions = [ - 'is_eda_exist', - 'build_step_space', - 'build_step_config', - 'run_step' - ] - - for func in functions: - if not hasattr(eda_module, func): - return False - return True - - eda_module = None + functions = ["is_eda_exist", "build_step_space", "build_step_config", "run_step"] + return all(hasattr(eda_module, func) for func in functions) + try: - import importlib + import importlib + eda_module = importlib.import_module(f"chipcompiler.tools.{eda_tool}") # check eda tool exist if not check_module(eda_module) or not eda_module.is_eda_exist(): logging.error(f"EDA tool : {eda_tool} not found!") return None - except Exception as e: + except Exception as e: logging.error(f"Error load module {eda_tool}: {e}") - finally: - return eda_module - -def create_step(workspace : Workspace, - step : str, - eda : str, - input_def : str, - input_verilog : str, - output_def : str = None, - output_verilog : str = None, - output_gds : str = None) -> WorkspaceStep: + return None + + return eda_module + + +def create_step( + workspace: Workspace, + step: str, + eda: str, + input_def: str, + input_verilog: str, + output_def: str = None, + output_verilog: str = None, + output_gds: str = None, +) -> WorkspaceStep: """ Create and return an EDA tool instance based on the given step and eda tool name. """ # check eda tool exist eda_module = load_eda_module(eda) - if eda_module is None \ - or not hasattr(eda_module, 'build_step'): + if eda_module is None or not hasattr(eda_module, "build_step"): return None - + # build step - step = eda_module.build_step(workspace=workspace, - step_name=step, - input_def=input_def, - input_verilog=input_verilog, - output_def=output_def, - output_verilog=output_verilog, - output_gds=output_gds) - + step = eda_module.build_step( + workspace=workspace, + step_name=step, + input_def=input_def, + input_verilog=input_verilog, + output_def=output_def, + output_verilog=output_verilog, + output_gds=output_gds, + ) + # build step sub workspace eda_module.build_step_space(step) - + # update config eda_module.build_step_config(workspace, step) - + return step -def run_step(workspace: Workspace, - step: WorkspaceStep, - module = None) -> bool: + +def run_step(workspace: Workspace, step: WorkspaceStep, module=None) -> bool: """ Run the given step using the provided EDA engine. """ @@ -77,13 +74,11 @@ def run_step(workspace: Workspace, eda_module = load_eda_module(step.tool) if eda_module is None: return False - - return eda_module.run_step(workspace=workspace, - step=step, - module=module) - -def save_layout_image(workspace: Workspace, - step: WorkspaceStep) -> bool: + + return eda_module.run_step(workspace=workspace, step=step, module=module) + + +def save_layout_image(workspace: Workspace, step: WorkspaceStep) -> bool: """ Save the layout image for the given step. """ @@ -91,13 +86,13 @@ def save_layout_image(workspace: Workspace, eda_module = load_eda_module("klayout") if eda_module is None: return False - + from chipcompiler.tools.klayout.runner import save_gds_image - return save_gds_image(workspace=workspace, - step=step) - -def build_step_metrics(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: + + return save_gds_image(workspace=workspace, step=step) + + +def build_step_metrics(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ build step metrics """ @@ -105,12 +100,10 @@ def build_step_metrics(workspace: Workspace, if eda_module is None: return False - return eda_module.build_step_metrics(workspace=workspace, - step=step) - -def get_step_info(workspace: Workspace, - step: WorkspaceStep, - id : str) -> dict: + return eda_module.build_step_metrics(workspace=workspace, step=step) + + +def get_step_info(workspace: Workspace, step: WorkspaceStep, id: str) -> dict: """ get step info by step and command id, return dict as resource definition """ @@ -118,6 +111,4 @@ def get_step_info(workspace: Workspace, if eda_module is None: return None - return eda_module.get_step_info(workspace=workspace, - step=step, - id=id) \ No newline at end of file + return eda_module.get_step_info(workspace=workspace, step=step, id=id) diff --git a/chipcompiler/tools/klayout/__init__.py b/chipcompiler/tools/klayout/__init__.py index 7f1e2b8d..7cbecf8c 100644 --- a/chipcompiler/tools/klayout/__init__.py +++ b/chipcompiler/tools/klayout/__init__.py @@ -1,29 +1,17 @@ -from .builder import ( - build_step, - build_step_space, - build_step_config -) - -from .runner import ( - run_step -) - +from .builder import build_step, build_step_config, build_step_space +from .module import KlayoutModule from .runner import ( + run_step, save_gds_image, ) - -from .module import KlayoutModule - -from .utility import ( - is_eda_exist -) +from .utility import is_eda_exist __all__ = [ - 'is_eda_exist', - 'build_step', - 'build_step_space', - 'build_step_config', - 'run_step', - 'KlayoutModule', - 'save_gds_image' -] \ No newline at end of file + "is_eda_exist", + "build_step", + "build_step_space", + "build_step_config", + "run_step", + "KlayoutModule", + "save_gds_image", +] diff --git a/chipcompiler/tools/klayout/builder.py b/chipcompiler/tools/klayout/builder.py index f75943cf..64aabdeb 100644 --- a/chipcompiler/tools/klayout/builder.py +++ b/chipcompiler/tools/klayout/builder.py @@ -1,11 +1,13 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- + def build_step(): pass + def build_step_space(): pass + def build_step_config(): - pass \ No newline at end of file + pass diff --git a/chipcompiler/tools/klayout/module.py b/chipcompiler/tools/klayout/module.py index acd15f1a..49ea5661 100644 --- a/chipcompiler/tools/klayout/module.py +++ b/chipcompiler/tools/klayout/module.py @@ -1,42 +1,39 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os -from chipcompiler.data import WorkspaceStep, Workspace, StateEnum, StepEnum + +from chipcompiler.data import Workspace, WorkspaceStep + class KlayoutModule: - def __init__(self, workspace : Workspace, step : WorkspaceStep): + def __init__(self, workspace: Workspace, step: WorkspaceStep): from chipcompiler.tools.klayout.utility import is_eda_exist + if not is_eda_exist(): raise ImportError("KLayout tool is not installed or not found.") self.workspace = workspace self.step = step - + def save_layout_image(self) -> bool: """ Save the layout image to the specified path. - """ + """ gds_file = self.step.output.get("gds", None) img_file = self.step.output.get("image", None) - + if gds_file is None or img_file is None or not os.path.exists(gds_file): return False - - self.save_snapshot_image(gds_file=gds_file, - img_file=img_file, - weight=1920, - height=1920) - return True - - def save_snapshot_image(self, - gds_file: str, - img_file: str, - weight: int = 1920, - height: int = 1920): + + self.save_snapshot_image(gds_file=gds_file, img_file=img_file, weight=1920, height=1920) + return True + + def save_snapshot_image( + self, gds_file: str, img_file: str, weight: int = 1920, height: int = 1920 + ): """ Takes a screenshot of a GDS file and saves it as an image. @Reference: https://gist.github.com/sequoiap/48af5f611cca838bb1ebc3008eef3a6e - + Args: gds_file (str): Path to the input GDS file img_file (str): Path to the output image file @@ -45,19 +42,19 @@ def save_snapshot_image(self, """ # Set display configuration options from klayout import lay - + lv = lay.LayoutView() lv.set_config("background-color", "#F5F5F5") # background of ECOS MERGE tab lv.set_config("grid-visible", "false") lv.set_config("grid-show-ruler", "false") lv.set_config("text-visible", "false") - + # Load the GDS file lv.load_layout(gds_file, 0) lv.max_hier() - + # Event processing for delayed configuration events lv.timer() - + # Save the image lv.save_image(img_file, weight, height) diff --git a/chipcompiler/tools/klayout/runner.py b/chipcompiler/tools/klayout/runner.py index 2f9775e9..0a799d9b 100644 --- a/chipcompiler/tools/klayout/runner.py +++ b/chipcompiler/tools/klayout/runner.py @@ -1,21 +1,19 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- from chipcompiler.data import Workspace, WorkspaceStep -from chipcompiler.tools.klayout.utility import is_eda_exist from chipcompiler.tools.klayout.module import KlayoutModule +from chipcompiler.tools.klayout.utility import is_eda_exist + def run_step(): pass -def save_gds_image(workspace: Workspace, - step: WorkspaceStep) -> bool: + +def save_gds_image(workspace: Workspace, step: WorkspaceStep) -> bool: """""" if not is_eda_exist(): return False - - klayout_module = KlayoutModule(workspace=workspace, - step=step) - + + klayout_module = KlayoutModule(workspace=workspace, step=step) + return klayout_module.save_layout_image() - \ No newline at end of file diff --git a/chipcompiler/tools/klayout/utility.py b/chipcompiler/tools/klayout/utility.py index b0f489b9..c7cbd3b7 100644 --- a/chipcompiler/tools/klayout/utility.py +++ b/chipcompiler/tools/klayout/utility.py @@ -1,12 +1,8 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- + def is_eda_exist() -> bool: - """ - Check if the KLayout tool is installed and accessible. - """ - try: - from klayout import lay - return True - except ImportError: - return False + """Check if the KLayout tool is installed and accessible.""" + import importlib.util + + return importlib.util.find_spec("klayout") is not None diff --git a/chipcompiler/tools/utility/__init__.py b/chipcompiler/tools/utility/__init__.py index 3ebd6b76..d51217c7 100644 --- a/chipcompiler/tools/utility/__init__.py +++ b/chipcompiler/tools/utility/__init__.py @@ -1,4 +1 @@ - -__all__ = [ - '' -] \ No newline at end of file +__all__ = [""] diff --git a/chipcompiler/tools/utility/update.py b/chipcompiler/tools/utility/update.py index ecf050a8..4265cc3e 100644 --- a/chipcompiler/tools/utility/update.py +++ b/chipcompiler/tools/utility/update.py @@ -1,3 +1 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep \ No newline at end of file diff --git a/chipcompiler/tools/yosys/__init__.py b/chipcompiler/tools/yosys/__init__.py index 6568a505..2b091629 100644 --- a/chipcompiler/tools/yosys/__init__.py +++ b/chipcompiler/tools/yosys/__init__.py @@ -1,41 +1,19 @@ -from .utility import ( - is_eda_exist -) - -from .builder import ( - build_step, - build_step_space, - build_step_config -) - -from .runner import ( - run_step -) - -from .metrics import ( - build_step_metrics -) - -from .service import( - get_step_info -) - -from .subflow import ( - YosysSubFlow -) - -from .checklist import ( - YosysChecklist -) +from .builder import build_step, build_step_config, build_step_space +from .checklist import YosysChecklist +from .metrics import build_step_metrics +from .runner import run_step +from .service import get_step_info +from .subflow import YosysSubFlow +from .utility import is_eda_exist __all__ = [ - 'is_eda_exist', - 'build_step', - 'build_step_space', - 'build_step_config', - 'run_step', - 'build_step_metrics', - 'get_step_info', - 'YosysSubFlow', - 'YosysChecklist' + "is_eda_exist", + "build_step", + "build_step_space", + "build_step_config", + "run_step", + "build_step_metrics", + "get_step_info", + "YosysSubFlow", + "YosysChecklist", ] diff --git a/chipcompiler/tools/yosys/builder.py b/chipcompiler/tools/yosys/builder.py index 67218477..fa349a77 100644 --- a/chipcompiler/tools/yosys/builder.py +++ b/chipcompiler/tools/yosys/builder.py @@ -1,14 +1,14 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os -from chipcompiler.data import WorkspaceStep, Workspace + +from chipcompiler.data import Workspace, WorkspaceStep def _tcl_quote(value: str) -> str: """Escape and quote TCL string values if needed.""" if not isinstance(value, str): value = str(value) - if ' ' in value: + if " " in value: escaped = value.replace('"', '\\"') return f'"{escaped}"' return value @@ -21,8 +21,7 @@ def _abspath(path: str) -> str: return os.path.abspath(path) -def generate_global_var_tcl(workspace: Workspace, - step: WorkspaceStep) -> str: +def generate_global_var_tcl(workspace: Workspace, step: WorkspaceStep) -> str: """Generate global_var.tcl content dynamically from workspace configuration.""" if not workspace.design.top_module: raise ValueError("TOP_NAME (workspace.design.top_module) not set") @@ -34,7 +33,11 @@ def generate_global_var_tcl(workspace: Workspace, raise ValueError(f"CLK_FREQ_MHZ must be positive number, got {freq_mhz}") rtl_file = step.input.get("verilog", "") - filelist = workspace.design.input_filelist if workspace.design.input_filelist else workspace.parameters.data.get("File list", "") + filelist = ( + workspace.design.input_filelist + if workspace.design.input_filelist + else workspace.parameters.data.get("File list", "") + ) # Prefer filelist if available, otherwise use rtl_file --- IGNORE --- has_valid_filelist = filelist and os.path.exists(filelist) @@ -44,7 +47,9 @@ def generate_global_var_tcl(workspace: Workspace, raise ValueError(f"Neither RTL_FILE ({rtl_file}) nor filelist ({filelist}) exists") top_design = workspace.design.top_module - clk_freq_mhz = int(freq_mhz) if isinstance(freq_mhz, float) and freq_mhz.is_integer() else freq_mhz + clk_freq_mhz = ( + int(freq_mhz) if isinstance(freq_mhz, float) and freq_mhz.is_integer() else freq_mhz + ) # Convert all paths to absolute since Yosys runs in script/ subdirectory netlist_file = _abspath(step.output.get("verilog", "")) @@ -69,10 +74,14 @@ def generate_global_var_tcl(workspace: Workspace, lib_files = workspace.pdk.libs if workspace.pdk.libs else [] lib_files_all = " ".join(lib_files) if lib_files else "" - lib_stdcell = lib_files.copy() #TODO: Distinguish between stdcell and other libraries? + lib_stdcell = lib_files.copy() # TODO: Distinguish between stdcell and other libraries? 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", "") + filelist = ( + workspace.design.input_filelist + if workspace.design.input_filelist + else workspace.parameters.data.get("File list", "") + ) if filelist: rtl_source_section = ( @@ -81,13 +90,12 @@ def generate_global_var_tcl(workspace: Workspace, ) else: rtl_source_section = ( - "# RTL source files\n" - f"set rtl_file [split {_tcl_quote(_abspath(rtl_file))}]" + f"# RTL source files\nset rtl_file [split {_tcl_quote(_abspath(rtl_file))}]" ) tcl_content = f"""# Auto-generated by builder.py - DO NOT EDIT MANUALLY # Generated configuration for Yosys synthesis -# Timestamp: {os.popen('date').read().strip()} +# Timestamp: {os.popen("date").read().strip()} set top_design {_tcl_quote(top_design)} set clk_freq_mhz {clk_freq_mhz} @@ -117,7 +125,7 @@ def generate_global_var_tcl(workspace: Workspace, set lib_list [split {_tcl_quote(lib_files_all) if lib_files_all else '""'}] # Working directories -set tmp_dir {_tcl_quote(os.path.join(data_dir, 'tmp'))} +set tmp_dir {_tcl_quote(os.path.join(data_dir, "tmp"))} ############### @@ -134,13 +142,15 @@ def generate_global_var_tcl(workspace: Workspace, return tcl_content -def build_step(workspace: Workspace, - step_name: str, - input_def: str, - input_verilog: str, - output_def: str = None, - output_verilog: str = None, - output_gds: str = None) -> WorkspaceStep: +def build_step( + workspace: Workspace, + step_name: str, + input_def: str, + input_verilog: str, + output_def: str = None, + output_verilog: str = None, + output_gds: str = None, +) -> WorkspaceStep: """ Build the synthesis step in the specified workspace. @@ -172,7 +182,7 @@ def build_step(workspace: Workspace, "verilog": output_verilog, "json": f"{step.directory}/output/{workspace.design.name}_{step.name}.json", "report": f"{step.directory}/output/{workspace.design.name}_{step.name}.rpt", - "image": f"{step.directory}/output/{workspace.design.name}_{step.name}.png" + "image": f"{step.directory}/output/{workspace.design.name}_{step.name}.png", } step.data = { @@ -204,20 +214,14 @@ def build_step(workspace: Workspace, step.analysis = { "dir": f"{step.directory}/analysis", - "metrics": f"{step.directory}/analysis/{step.name}_metrics.json" - } - + "metrics": f"{step.directory}/analysis/{step.name}_metrics.json", + } + # build sub flow paths and data - step.subflow = { - "path": f"{step.directory}/subflow.json", - "steps": [] - } - + step.subflow = {"path": f"{step.directory}/subflow.json", "steps": []} + # build checklist paths and data - step.checklist = { - "path": f"{step.directory}/checklist.json", - "checklist": [] - } + step.checklist = {"path": f"{step.directory}/checklist.json", "checklist": []} return step @@ -238,71 +242,68 @@ def build_step_space(step: WorkspaceStep) -> None: os.makedirs(step.analysis.get("dir", f"{step.directory}/analysis"), exist_ok=True) -def build_step_config(workspace: Workspace, - step: WorkspaceStep): +def build_step_config(workspace: Workspace, step: WorkspaceStep): """ Build the configuration files for the synthesis step. Creates the following directory structure: - - script/ subdirectory with all prepared TCL scripts (yosys_synthesis.tcl, init_tech.tcl, abc-opt.script, aig library) - - data/ subdirectory with generated files (global_var.tcl, tmp/ folder with generated temporary constr files) + - script/ subdirectory with prepared TCL scripts + (yosys_synthesis.tcl, init_tech.tcl, abc-opt.script, aig library) + - data/ subdirectory with generated files + (global_var.tcl, tmp/ folder with generated temporary constr files) """ import shutil current_dir = os.path.dirname(os.path.abspath(__file__)) - scripts_dir = os.path.abspath(os.path.join(current_dir, 'scripts')) + scripts_dir = os.path.abspath(os.path.join(current_dir, "scripts")) - for file in ['yosys_synthesis.tcl', 'init_tech.tcl']: + for file in ["yosys_synthesis.tcl", "init_tech.tcl"]: src = os.path.join(scripts_dir, file) if os.path.exists(src): - shutil.copy2(src, os.path.join(step.script['dir'], file)) + shutil.copy2(src, os.path.join(step.script["dir"], file)) - abc_script = os.path.join(scripts_dir, 'abc-opt.script') + abc_script = os.path.join(scripts_dir, "abc-opt.script") if os.path.exists(abc_script): - shutil.copy2(abc_script, os.path.join(step.script['dir'], 'abc-opt.script')) + shutil.copy2(abc_script, os.path.join(step.script["dir"], "abc-opt.script")) - configs_dir = os.path.abspath(os.path.join(current_dir, 'configs')) - aig_file = os.path.join(configs_dir, 'lazy_man_synth_library.aig') + configs_dir = os.path.abspath(os.path.join(current_dir, "configs")) + aig_file = os.path.join(configs_dir, "lazy_man_synth_library.aig") if os.path.exists(aig_file): - shutil.copy2(aig_file, os.path.join(step.script['dir'], 'lazy_man_synth_library.aig')) + shutil.copy2(aig_file, os.path.join(step.script["dir"], "lazy_man_synth_library.aig")) try: tcl_content = generate_global_var_tcl(workspace, step) - global_var_path = os.path.join(step.data['dir'], 'global_var.tcl') - with open(global_var_path, 'w') as f: + global_var_path = os.path.join(step.data["dir"], "global_var.tcl") + with open(global_var_path, "w") as f: f.write(tcl_content) - except (ValueError, IOError) as e: + except (OSError, ValueError) as e: print(f"Error generating global_var.tcl: {e}") raise - + # build subflow json - build_sub_flow(workspace=workspace, - workspace_step=step) - - build_checklist(workspace=workspace, - workspace_step=step) + build_sub_flow(workspace=workspace, workspace_step=step) + build_checklist(workspace=workspace, workspace_step=step) -def build_sub_flow(workspace : Workspace, - workspace_step : WorkspaceStep): + +def build_sub_flow(workspace: Workspace, workspace_step: WorkspaceStep): from .subflow import YosysSubFlow - subflow = YosysSubFlow(workspace=workspace, - workspace_step=workspace_step) - - subflow.build_sub_flow() - -def build_checklist(workspace : Workspace, - workspace_step : WorkspaceStep): + + subflow = YosysSubFlow(workspace=workspace, workspace_step=workspace_step) + + subflow.build_sub_flow() + + +def build_checklist(workspace: Workspace, workspace_step: WorkspaceStep): from .checklist import YosysChecklist - checklist = YosysChecklist(workspace=workspace, - workspace_step=workspace_step) - - checklist.build_checklist() - -def build_environment(workspace: Workspace, - step: WorkspaceStep): + + checklist = YosysChecklist(workspace=workspace, workspace_step=workspace_step) + + checklist.build_checklist() + + +def build_environment(workspace: Workspace, step: WorkspaceStep): """ Build the environment for the given step. """ pass - diff --git a/chipcompiler/tools/yosys/checklist.py b/chipcompiler/tools/yosys/checklist.py index 6f48b750..530d2d0d 100644 --- a/chipcompiler/tools/yosys/checklist.py +++ b/chipcompiler/tools/yosys/checklist.py @@ -1,16 +1,17 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StepEnum, CheckState +from chipcompiler.data import CheckState, Workspace, WorkspaceStep + class YosysChecklist: - def __init__(self, workspace : Workspace, workspace_step: WorkspaceStep): + def __init__(self, workspace: Workspace, workspace_step: WorkspaceStep): self.workspace = workspace self.workspace_step = workspace_step - + self.init_checklist() - + def init_checklist(self): from chipcompiler.utility import json_read + data = json_read(self.workspace_step.checklist.get("path", "")) if len(data) > 0: self.workspace_step.checklist["checklist"] = data.get("checklist", []) @@ -18,41 +19,38 @@ def init_checklist(self): self.build_checklist() def build_checklist(self) -> list: - def checklist_template(check_item : str, - description : str): + def checklist_template(check_item: str, description: str): return { - "name" : check_item, - "description" : description, - "state" : CheckState.Unstart.value - } - + "name": check_item, + "description": description, + "state": CheckState.Unstart.value, + } + checklist = [] - - checklist.append(checklist_template(check_item="check_item_1", - description="check item 1")) - checklist.append(checklist_template(check_item="check_item_2", - description="check item 2")) - + + checklist.append(checklist_template(check_item="check_item_1", description="check item 1")) + checklist.append(checklist_template(check_item="check_item_2", description="check item 2")) + self.workspace_step.checklist["checklist"] = checklist - + self.save() - + def save(self) -> bool: from chipcompiler.utility import json_write - - return json_write(file_path=self.workspace_step.checklist.get("path", ""), - data=self.workspace_step.checklist) - - def update_item(self, - check_item : str, - state : str | CheckState): + + return json_write( + file_path=self.workspace_step.checklist.get("path", ""), + data=self.workspace_step.checklist, + ) + + def update_item(self, check_item: str, state: str | CheckState): state = state.value if isinstance(state, CheckState) else state - + for item_dict in self.workspace_step.checklist.get("checklist", []): if item_dict.get("name") == check_item: item_dict["state"] = state self.save() - + def check(self): - pass \ No newline at end of file + pass diff --git a/chipcompiler/tools/yosys/metrics.py b/chipcompiler/tools/yosys/metrics.py index 80d97306..7d4e9787 100644 --- a/chipcompiler/tools/yosys/metrics.py +++ b/chipcompiler/tools/yosys/metrics.py @@ -1,11 +1,9 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StepMetrics, save_metrics +from chipcompiler.data import StepMetrics, Workspace, WorkspaceStep, save_metrics from chipcompiler.utility import json_read -def build_step_metrics(workspace: Workspace, - step: WorkspaceStep) -> StepMetrics: +def build_step_metrics(workspace: Workspace, step: WorkspaceStep) -> StepMetrics: """ Build and persist synthesis metrics from Yosys stat JSON. Args: @@ -15,14 +13,14 @@ def build_step_metrics(workspace: Workspace, StepMetrics: The populated step metrics object, or None if not available. """ step_metrics = StepMetrics() - step_metrics.path = step.analysis.get('metrics', '') + step_metrics.path = step.analysis.get("metrics", "") - stat_json_path = step.feature.get('stat') + stat_json_path = step.feature.get("stat") data = json_read(stat_json_path) if not data: return None - design_data = data.get('design', {}) + design_data = data.get("design", {}) metrics = { "Step": step.name, @@ -44,4 +42,4 @@ def build_step_metrics(workspace: Workspace, if save_metrics(step_metrics): return step_metrics - return None \ No newline at end of file + return None diff --git a/chipcompiler/tools/yosys/runner.py b/chipcompiler/tools/yosys/runner.py index d394fc55..0d25f6f6 100644 --- a/chipcompiler/tools/yosys/runner.py +++ b/chipcompiler/tools/yosys/runner.py @@ -1,17 +1,15 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import subprocess import os -from chipcompiler.data import WorkspaceStep, Workspace, StateEnum, StepEnum -from chipcompiler.tools.yosys.utility import is_eda_exist, get_yosys_command +import subprocess + +from chipcompiler.data import StateEnum, Workspace, WorkspaceStep +from chipcompiler.tools.yosys.checklist import YosysChecklist from chipcompiler.tools.yosys.metrics import build_step_metrics from chipcompiler.tools.yosys.subflow import YosysSubFlow -from chipcompiler.tools.yosys.checklist import YosysChecklist +from chipcompiler.tools.yosys.utility import get_yosys_command, is_eda_exist -def run_step(workspace: Workspace, - step: WorkspaceStep, - module=None) -> bool: +def run_step(workspace: Workspace, step: WorkspaceStep, module=None) -> bool: """ Run the synthesis step using yosys. @@ -28,7 +26,7 @@ def run_step(workspace: Workspace, """ if not is_eda_exist(): return False - + sub_flow = YosysSubFlow(workspace=workspace, workspace_step=step) input_verilog = step.input.get("verilog", "") @@ -53,28 +51,28 @@ def run_step(workspace: Workspace, cmd = yosys_cmd + ["yosys_synthesis.tcl"] with open(step.log["file"], "w") as log_file: - result = subprocess.run( + subprocess.run( cmd, cwd=cwd_dir, env=os.environ.copy(), stdout=log_file, stderr=subprocess.STDOUT, - timeout=600 + timeout=600, ) if os.path.exists(step.output["verilog"]): sub_flow.update_step(step_name="run yosys", state=StateEnum.Success) - + build_step_metrics(workspace=workspace, step=step) - + sub_flow.update_step(step_name="analysis", state=StateEnum.Success) - + checklist = YosysChecklist(workspace=workspace, workspace_step=step) checklist.check() return True else: sub_flow.update_step(step_name="run yosys", state=StateEnum.Invalid) - + print(f"Error: Output netlist not generated at {step.output['verilog']}") return False diff --git a/chipcompiler/tools/yosys/service.py b/chipcompiler/tools/yosys/service.py index 3765a356..1f200563 100644 --- a/chipcompiler/tools/yosys/service.py +++ b/chipcompiler/tools/yosys/service.py @@ -1,16 +1,13 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StepMetrics, save_metrics -from chipcompiler.tools.yosys.metrics import build_step_metrics +from chipcompiler.data import Workspace, WorkspaceStep -def get_step_info(workspace: Workspace, - step: WorkspaceStep, - id : str) -> dict: + +def get_step_info(workspace: Workspace, step: WorkspaceStep, id: str) -> dict: """ get step info by step and command id, return dict as resource definition """ step_info = {} - + match id: case "views": step_info = build_views(workspace=workspace, step=step) @@ -29,62 +26,54 @@ def get_step_info(workspace: Workspace, return step_info -def build_views(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_views(workspace: Workspace, step: WorkspaceStep) -> dict: info = { - "image" : step.output.get("image", ""), - "metrics" : step.analysis.get('metrics', ''), - "information" : {} + "image": step.output.get("image", ""), + "metrics": step.analysis.get("metrics", ""), + "information": {}, } - + return info -def build_layout(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_layout(workspace: Workspace, step: WorkspaceStep) -> dict: info = { - "image" : step.output.get("image", ""), + "image": step.output.get("image", ""), } - + return info -def build_metrics(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "metrics" : step.analysis.get('metrics', '') - } - + +def build_metrics(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"metrics": step.analysis.get("metrics", "")} + return info -def build_subflow(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "path" : step.subflow.get("path", "") - } - + +def build_subflow(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"path": step.subflow.get("path", "")} + return info -def build_analysis(workspace: Workspace, - step: WorkspaceStep) -> dict: + +def build_analysis(workspace: Workspace, step: WorkspaceStep) -> dict: info = { - "metrics" : step.analysis.get("metrics", ""), - "data summary" : step.feature.get("stat", ""), - "step report" : step.report.get("check", "") + "metrics": step.analysis.get("metrics", ""), + "data summary": step.feature.get("stat", ""), + "step report": step.report.get("check", ""), } - + return info -def build_maps(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - - } - + +def build_maps(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {} + return info -def build_checklist(workspace: Workspace, - step: WorkspaceStep) -> dict: - info = { - "path" : step.checklist.get("path", "") - } - - return info \ No newline at end of file + +def build_checklist(workspace: Workspace, step: WorkspaceStep) -> dict: + info = {"path": step.checklist.get("path", "")} + + return info diff --git a/chipcompiler/tools/yosys/subflow.py b/chipcompiler/tools/yosys/subflow.py index 29837a5f..4ec85cca 100644 --- a/chipcompiler/tools/yosys/subflow.py +++ b/chipcompiler/tools/yosys/subflow.py @@ -1,16 +1,17 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from chipcompiler.data import Workspace, WorkspaceStep, StateEnum, StepEnum +from chipcompiler.data import StateEnum, Workspace, WorkspaceStep + class YosysSubFlow: - def __init__(self, workspace : Workspace, workspace_step: WorkspaceStep): + def __init__(self, workspace: Workspace, workspace_step: WorkspaceStep): self.workspace = workspace self.workspace_step = workspace_step - + self.init_sub_flow() - + def init_sub_flow(self): from chipcompiler.utility import json_read + data = json_read(self.workspace_step.subflow.get("path", "")) if len(data) > 0: self.workspace_step.subflow["steps"] = data.get("steps", []) @@ -18,43 +19,49 @@ def init_sub_flow(self): self.build_sub_flow() def build_sub_flow(self) -> list: - def subflow_template(step_name : str): + def subflow_template(step_name: str): return { - "name" : step_name, # step name - "state" : StateEnum.Unstart.value, # step state - "runtime" : "", # step run time - "peak memory (mb)" : 0, # step peak memory - "info" : {} # step additional infomation - } - + "name": step_name, # step name + "state": StateEnum.Unstart.value, # step state + "runtime": "", # step run time + "peak memory (mb)": 0, # step peak memory + "info": {}, # step additional infomation + } + steps = [] - + steps.append(subflow_template("run yosys")) steps.append(subflow_template("analysis")) - + self.workspace_step.subflow["steps"] = steps - + self.save() - + def save(self) -> bool: from chipcompiler.utility import json_write - - return json_write(file_path=self.workspace_step.subflow.get("path", ""), - data=self.workspace_step.subflow) - - def update_step(self, - step_name : str, - state : str | StateEnum, - runtime : str = "", - memory : float = 0, - info : dict = {}): + + return json_write( + file_path=self.workspace_step.subflow.get("path", ""), data=self.workspace_step.subflow + ) + + def update_step( + self, + step_name: str, + state: str | StateEnum, + runtime: str = "", + memory: float = 0, + info: dict | None = None, + ): + if info is None: + info = {} + state = state.value if isinstance(state, StateEnum) else state - + for step_dict in self.workspace_step.subflow.get("steps", []): if step_dict.get("name") == step_name: step_dict["state"] = state step_dict["runtime"] = runtime step_dict["peak memory (mb)"] = memory step_dict["info"] = info - - self.save() \ No newline at end of file + + self.save() diff --git a/chipcompiler/tools/yosys/utility.py b/chipcompiler/tools/yosys/utility.py index 3e116dc4..9ff10699 100644 --- a/chipcompiler/tools/yosys/utility.py +++ b/chipcompiler/tools/yosys/utility.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import shutil diff --git a/chipcompiler/utility/__init__.py b/chipcompiler/utility/__init__.py index d69609b8..c75652cb 100644 --- a/chipcompiler/utility/__init__.py +++ b/chipcompiler/utility/__init__.py @@ -1,53 +1,33 @@ -from .file import ( - chmod_folder, - find_files -) - -from .json import ( - json_read, - json_write, - dict_to_str -) - -from .log import Logger, create_logger - -from .util import ( - track_process_memory -) - -from .plot import ( - plot_csv_map, - plot_metrics, - plot_csv_table -) - +from .csv import csv_write +from .file import chmod_folder, find_files from .filelist import ( + get_filelist_info, parse_filelist, + parse_incdir_directives, resolve_path, validate_filelist, - get_filelist_info, - parse_incdir_directives -) - -from .csv import ( - csv_write ) +from .json import dict_to_str, json_read, json_write +from .log import Logger, create_logger +from .plot import plot_csv_map, plot_csv_table, plot_metrics +from .util import track_process_memory __all__ = [ - 'chmod_folder', - 'json_read', - 'json_write', - 'dict_to_str', - 'Logger', - 'create_logger', - 'track_process_memory', - 'plot_csv_map', - 'plot_metrics', - 'plot_csv_table', - 'parse_filelist', - 'resolve_path', - 'validate_filelist', - 'get_filelist_info', - 'csv_write', - 'parse_incdir_directives' + "chmod_folder", + "find_files", + "json_read", + "json_write", + "dict_to_str", + "Logger", + "create_logger", + "track_process_memory", + "plot_csv_map", + "plot_metrics", + "plot_csv_table", + "parse_filelist", + "resolve_path", + "validate_filelist", + "get_filelist_info", + "csv_write", + "parse_incdir_directives", ] diff --git a/chipcompiler/utility/csv.py b/chipcompiler/utility/csv.py index 928c454e..9e29ba45 100644 --- a/chipcompiler/utility/csv.py +++ b/chipcompiler/utility/csv.py @@ -1,33 +1,29 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import os import csv def csv_write(file_path: str, header, data) -> bool: - with open(file_path, 'w', newline='') as csvfile: - writer = csv.writer(csvfile) - # writer = csv.DictWriter(csvfile) - - writer.writerows(header) - writer.writerows(data) - - return True - - # Write headers - writer.writeheader() - - # Write data rows - for workspace_data in data: - row = {} - # Add counts for each layer, defaulting to 0 if not present - - for idnex, item in enumerate(workspace_data): - row[header[idnex]] = item - writer.writerow(row) - - return True + with open(file_path, "w", newline="") as csvfile: + writer = csv.writer(csvfile) + # writer = csv.DictWriter(csvfile) + + writer.writerows(header) + writer.writerows(data) + + return True + + # Write headers + writer.writeheader() + + # Write data rows + for workspace_data in data: + row = {} + # Add counts for each layer, defaulting to 0 if not present + + for idnex, item in enumerate(workspace_data): + row[header[idnex]] = item + writer.writerow(row) + + return True return False - - \ No newline at end of file diff --git a/chipcompiler/utility/file.py b/chipcompiler/utility/file.py index 154821d9..7a62f076 100644 --- a/chipcompiler/utility/file.py +++ b/chipcompiler/utility/file.py @@ -1,27 +1,27 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os +from contextlib import suppress -def chmod_folder(folder:str, mode:int = 0o777): - def _try_chmod(self, path): - try: - os.chmod(path, 0o777) - except Exception: - pass +def chmod_folder(folder: str, mode: int = 0o777): for root, dirs, files in os.walk(folder): - _try_chmod(root) + with suppress(Exception): + os.chmod(root, mode) + for file in files: - _try_chmod(os.path.join(root, file)) - for dir in dirs: - full_path = os.path.join(root, dir) - _try_chmod(full_path) + with suppress(Exception): + os.chmod(os.path.join(root, file), mode) + + for dir_ in dirs: + with suppress(Exception): + os.chmod(os.path.join(root, dir_), mode) + -def find_files(directory : str, key : str): +def find_files(directory: str, key: str): result_files = [] - for root, dirs, files in os.walk(directory): + for root, _dirs, files in os.walk(directory): for file in files: if file.endswith(f"{key}"): result_files.append(os.path.join(root, file)) - return result_files \ No newline at end of file + return result_files diff --git a/chipcompiler/utility/filelist.py b/chipcompiler/utility/filelist.py index 388fec33..174211cc 100644 --- a/chipcompiler/utility/filelist.py +++ b/chipcompiler/utility/filelist.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- """ Filelist parsing utilities for handling EDA tool filelist files. @@ -24,17 +23,15 @@ """ import os -from typing import List - UNSUPPORTED_OPTIONS = { - '-f': 'Recursive filelist files', - '-v': 'Library files', - '-y': 'Library search directories', + "-f": "Recursive filelist files", + "-v": "Library files", + "-y": "Library search directories", } -def parse_filelist(filelist_path: str) -> List[str]: +def parse_filelist(filelist_path: str) -> list[str]: """ Parse a filelist file and extract all file paths. @@ -58,7 +55,7 @@ def parse_filelist(filelist_path: str) -> List[str]: file_paths = [] - with open(filelist_path, 'r', encoding='utf-8') as f: + with open(filelist_path, encoding="utf-8") as f: for line_num, line in enumerate(f, 1): path = _parse_line(line, line_num) if path: @@ -77,18 +74,20 @@ def _parse_line(line: str, line_num: int) -> str | None: line = line.strip() # Skip empty lines and comment lines - if not line or line.startswith(('#', '//', '`')): + if not line or line.startswith(("#", "//", "`")): return None # Skip +incdir and other + options - if line.startswith('+'): + if line.startswith("+"): return None # Handle - options - if line.startswith('-'): + if line.startswith("-"): option = line.split()[0] if option in UNSUPPORTED_OPTIONS: - descriptions = '\n'.join(f" {opt}: {desc}" for opt, desc in UNSUPPORTED_OPTIONS.items()) + descriptions = "\n".join( + f" {opt}: {desc}" for opt, desc in UNSUPPORTED_OPTIONS.items() + ) raise ValueError( f"Unsupported filelist option at line {line_num}: '{option}'\n" f"The parser does not support:\n" @@ -99,14 +98,14 @@ def _parse_line(line: str, line_num: int) -> str | None: # Remove inline comments and quotes line = _remove_inline_comment(line) - return line.strip('"\'') or None + return line.strip("\"'") or None def _remove_inline_comment(line: str) -> str: """Remove inline comments from a line.""" - for marker in ('#', '//'): + for marker in ("#", "//"): if marker in line: - line = line[:line.index(marker)].strip() + line = line[: line.index(marker)].strip() return line @@ -132,7 +131,7 @@ def resolve_path(path: str, base_dir: str) -> str: return os.path.abspath(os.path.join(base_dir, path)) -def validate_filelist(filelist_path: str) -> tuple[List[str], List[str]]: +def validate_filelist(filelist_path: str) -> tuple[list[str], list[str]]: """ Validate a filelist by checking if all referenced files exist. @@ -188,16 +187,16 @@ def get_filelist_info(filelist_path: str) -> dict: file_sizes = _compute_file_sizes(existing_files, filelist_dir) return { - 'filelist': abs_filelist, - 'base_dir': filelist_dir, - 'total_files': len(existing_files) + len(missing_files), - 'existing_files': existing_files, - 'missing_files': missing_files, - 'file_sizes': file_sizes + "filelist": abs_filelist, + "base_dir": filelist_dir, + "total_files": len(existing_files) + len(missing_files), + "existing_files": existing_files, + "missing_files": missing_files, + "file_sizes": file_sizes, } -def _compute_file_sizes(file_paths: List[str], base_dir: str) -> dict: +def _compute_file_sizes(file_paths: list[str], base_dir: str) -> dict: """Compute file sizes for a list of file paths.""" file_sizes = {} for file_path in file_paths: @@ -209,7 +208,7 @@ def _compute_file_sizes(file_paths: List[str], base_dir: str) -> dict: return file_sizes -def parse_incdir_directives(filelist_path: str) -> List[str]: +def parse_incdir_directives(filelist_path: str) -> list[str]: """ Parse +incdir directives from a filelist file. @@ -232,14 +231,14 @@ def parse_incdir_directives(filelist_path: str) -> List[str]: incdir_paths = [] - with open(filelist_path, 'r', encoding='utf-8') as f: + with open(filelist_path, encoding="utf-8") as f: for line in f: line = line.strip() - if not line or line.startswith(('#', '//', '`')): + if not line or line.startswith(("#", "//", "`")): continue - if line.startswith('+incdir+'): + if line.startswith("+incdir+"): path = _extract_incdir_path(line) if path: incdir_paths.append(path) @@ -249,6 +248,6 @@ def parse_incdir_directives(filelist_path: str) -> List[str]: def _extract_incdir_path(line: str) -> str: """Extract path from +incdir+ directive, handling comments and quotes.""" - path = line.removeprefix('+incdir+') + path = line.removeprefix("+incdir+") path = _remove_inline_comment(path).strip() - return path.strip('"\'') or "" + return path.strip("\"'") or "" diff --git a/chipcompiler/utility/json.py b/chipcompiler/utility/json.py index fd38d2ba..e5d04e9e 100644 --- a/chipcompiler/utility/json.py +++ b/chipcompiler/utility/json.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import os import json +import os + def json_read(file_path: str) -> dict: """ @@ -11,42 +11,47 @@ def json_read(file_path: str) -> dict: data = {} if os.path.isfile(file_path) is False: return data - + try: - if file_path.endswith('.gz'): + if file_path.endswith(".gz"): import gzip - with gzip.open(file_path, 'rt') as f: + + with gzip.open(file_path, "rt") as f: data = json.load(f) else: - with open(file_path, 'r') as f: + with open(file_path) as f: data = json.load(f) - except Exception as e: + except Exception: return data - + return data -def json_write(file_path: str, data: dict={}, indent=4) -> bool: - """ - Write a dictionary to a JSON file. - """ + +def json_write(file_path: str, data: dict | None = None, indent=4) -> bool: + """Write a dictionary to a JSON file.""" + if data is None: + data = {} + try: - if file_path.endswith('.gz'): + if file_path.endswith(".gz"): import gzip - with gzip.open(file_path, 'wt') as f: + + with gzip.open(file_path, "wt") as f: json.dump(data, f, indent=indent) else: - with open(file_path, 'w') as f: + with open(file_path, "w") as f: json.dump(data, f, indent=indent) return True - except Exception as e: + except Exception: return False - + + def dict_to_str(d, indent=0): result = [] for key, value in d.items(): - result.append(' ' * indent + f"{key}:") + result.append(" " * indent + f"{key}:") if isinstance(value, dict): result.append(dict_to_str(value, indent + 1)) else: - result.append(' ' * (indent + 1) + str(value)) - return '\n'.join(result) \ No newline at end of file + result.append(" " * (indent + 1) + str(value)) + return "\n".join(result) diff --git a/chipcompiler/utility/log.py b/chipcompiler/utility/log.py index c2546e7b..40500d52 100644 --- a/chipcompiler/utility/log.py +++ b/chipcompiler/utility/log.py @@ -1,20 +1,18 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import os import logging -from logging.handlers import RotatingFileHandler +import os import sys -from typing import Optional import time +from logging.handlers import RotatingFileHandler class Logger: def __init__( self, name: str = "ecc", - log_file: Optional[str] = None, - log_dir: Optional[str] = None, + log_file: str | None = None, + log_dir: str | None = None, max_bytes: int = 10 * 1024 * 1024, # 10MB backup_count: int = 5, level: int = logging.INFO, @@ -29,9 +27,13 @@ def __init__( console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(formatter) self.logger.addHandler(console_handler) - + if log_file or log_dir: - file = log_file if log_file else f"{log_dir}/{name}.{time.strftime('%Y-%m-%d_%H-%M-%S')}" + file = ( + log_file + if log_file + else f"{log_dir}/{name}.{time.strftime('%Y-%m-%d_%H-%M-%S')}" + ) file_handler = RotatingFileHandler( file, maxBytes=max_bytes, backupCount=backup_count ) @@ -56,8 +58,8 @@ def critical(self, msg: str, *args, **kwargs): def create_logger( name: str = "ecc", - log_file: Optional[str] = None, - log_dir: Optional[str] = None, + log_file: str | None = None, + log_dir: str | None = None, max_bytes: int = 10 * 1024 * 1024, # 10MB backup_count: int = 5, level: int = logging.INFO, @@ -80,7 +82,7 @@ def create_logger( backup_count=backup_count, level=level, fmt=fmt, - ) + ) else: return Logger( name=name, diff --git a/chipcompiler/utility/plot.py b/chipcompiler/utility/plot.py index 3458fc32..5829e934 100644 --- a/chipcompiler/utility/plot.py +++ b/chipcompiler/utility/plot.py @@ -1,135 +1,139 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- import os -import pandas as pd + import matplotlib -matplotlib.use('Agg') # Use non-interactive backend for multi-threading +import pandas as pd + +matplotlib.use("Agg") # Use non-interactive backend for multi-threading import matplotlib.pyplot as plt -def plot_csv_map(input_path : str, output_path : str=None) -> bool: + +def plot_csv_map(input_path: str, output_path: str = None) -> bool: """ Plot array map from CSV file. - + Args: input_path (str): Path to the input CSV file. output_path (str, optional): Path to the output PNG file. Defaults to None. - + Returns: bool: True if the plot is successful, False otherwise. """ if not os.path.exists(input_path) or not input_path.lower().endswith(".csv"): return False - + if not output_path: output_path = input_path.replace(".csv", ".png") - + try: # Read CSV data df = pd.read_csv(input_path) - + # Create a new figure and axis for each thread fig, ax = plt.subplots(figsize=(10, 8)) - + # Assuming the CSV has columns that can be used as x, y, and value # If the CSV has a grid structure without headers, we can use it directly if df.shape[1] > 1: # Try different approaches based on CSV structure - if 'x' in df.columns and 'y' in df.columns and 'value' in df.columns: + if "x" in df.columns and "y" in df.columns and "value" in df.columns: # For CSV with x, y, value columns (scatter plot with color) - scatter = ax.scatter(df['x'], df['y'], c=df['value'], cmap='viridis') - fig.colorbar(scatter, ax=ax, label='Value') + scatter = ax.scatter(df["x"], df["y"], c=df["value"], cmap="viridis") + fig.colorbar(scatter, ax=ax, label="Value") else: # For matrix-like CSV data (heatmap) - img = ax.imshow(df.values, cmap='viridis', origin='lower') - fig.colorbar(img, ax=ax, label='Value') + img = ax.imshow(df.values, cmap="viridis", origin="lower") + fig.colorbar(img, ax=ax, label="Value") ax.set_xticks(range(df.shape[1])) - ax.set_xticklabels(df.columns if df.columns[0] != '0' else range(df.shape[1])) + ax.set_xticklabels(df.columns if df.columns[0] != "0" else range(df.shape[1])) ax.set_yticks(range(df.shape[0])) else: # For 1D data ax.plot(df.values.flatten()) - + title, _ = os.path.splitext(os.path.basename(input_path)) ax.set_title(title) - ax.set_xlabel('X') - ax.set_ylabel('Y') - + ax.set_xlabel("X") + ax.set_ylabel("Y") + # Save the plot - fig.savefig(output_path, dpi=300, bbox_inches='tight') - + fig.savefig(output_path, dpi=300, bbox_inches="tight") + # Clean up plt.close(fig) - + return True - except Exception as e: - plt.close('all') + except Exception: + plt.close("all") return False - -def plot_csv_table(input_path: str, output_path: str=None) -> bool: + + +def plot_csv_table(input_path: str, output_path: str = None) -> bool: """ Plot CSV data as a table and save to output path. - + Args: input_path (str): Path to the input CSV file. output_path (str, optional): Path to save the output PNG file. Defaults to None. - + Returns: bool: True if the plot is successful, False otherwise. """ if not os.path.exists(input_path) or not input_path.lower().endswith(".csv"): return False - + if not output_path: output_path = input_path.replace(".csv", ".png") - + try: # Read CSV data df = pd.read_csv(input_path) - + # Create figure and axis fig, ax = plt.subplots(figsize=(len(df.columns) * 2, len(df) * 0.5)) - ax.axis('tight') - ax.axis('off') - + ax.axis("tight") + ax.axis("off") + # Create table - table = ax.table(cellText=df.values, colLabels=df.columns, cellLoc='center', loc='center') - + table = ax.table(cellText=df.values, colLabels=df.columns, cellLoc="center", loc="center") + # Set table style table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1.1, 1.5) - + # Save the plot - fig.savefig(output_path, dpi=300, bbox_inches='tight') + fig.savefig(output_path, dpi=300, bbox_inches="tight") plt.close(fig) - + return True - except Exception as e: - plt.close('all') + except Exception: + plt.close("all") return False - -def plot_metrics(metrics: dict, output_path: str=None, col=4) -> bool: + + +def plot_metrics(metrics: dict, output_path: str = None, col=4) -> bool: """ Plot metrics as a table and save to output path. - + Args: metrics (dict): Dictionary of metrics to plot. output_path (str): Path to save the output PNG file. col (int): Number of columns in the table. - + Returns: bool: True if the plot is successful, False otherwise. - """ + """ try: import math - + keys = list(metrics.keys()) values = list(metrics.values()) - + # Calculate number of rows needed num_metrics = len(metrics) rows = math.ceil(num_metrics / col) - + # Create table data with col columns and rows rows table_data = [] for i in range(rows): @@ -143,39 +147,39 @@ def plot_metrics(metrics: dict, output_path: str=None, col=4) -> bool: row_data.append("") row_data.append("") table_data.append(row_data) - + # Calculate figure size based on number of rows and columns fig_width = 2 * col # Each column pair (key-value) takes about 2 inches - fig_height = rows # Each row takes about 1 inches - + fig_height = rows # Each row takes about 1 inches + # Create figure and table fig, ax = plt.subplots(figsize=(fig_width, fig_height)) - ax.axis('tight') - ax.axis('off') - + ax.axis("tight") + ax.axis("off") + # Create table - table = ax.table(cellText=table_data, cellLoc='left', loc='center') - + table = ax.table(cellText=table_data, cellLoc="left", loc="center") + # Set table style table.auto_set_font_size(False) table.set_fontsize(8) table.scale(2, 1) # Scale table for better readability - + # Make key fonts bold for i in range(len(table_data)): for j in range(0, len(table_data[i]), 2): # Keys are in even indices (0, 2, 4, ...) if table_data[i][j]: # Only set style if cell is not empty - table[(i, j)].set_text_props(weight='bold') - + table[(i, j)].set_text_props(weight="bold") + # Set title # plt.title('Metrics Overview', fontsize=16, pad=20) - + # Save the plot - fig.savefig(output_path, dpi=300, bbox_inches='tight') + fig.savefig(output_path, dpi=300, bbox_inches="tight") plt.close(fig) - + return True except Exception as e: print(f"Error plotting metrics: {e}") - plt.close('all') - return False \ No newline at end of file + plt.close("all") + return False diff --git a/chipcompiler/utility/util.py b/chipcompiler/utility/util.py index fb4fc94a..28c91bb3 100644 --- a/chipcompiler/utility/util.py +++ b/chipcompiler/utility/util.py @@ -1,12 +1,13 @@ import os import time + def track_process_memory(pid): peak_rss_kb = 0 try: # check memory usage every 0.1 second while os.path.exists(f"/proc/{pid}/status"): - with open(f"/proc/{pid}/status", 'r') as f: + with open(f"/proc/{pid}/status") as f: for line in f: if line.startswith("VmRSS:"): # VmRSS is in kB @@ -14,7 +15,7 @@ def track_process_memory(pid): peak_rss_kb = max(peak_rss_kb, rss_kb) break time.sleep(0.1) - except Exception as e: + except Exception: # ignore errors pass - return peak_rss_kb \ No newline at end of file + return peak_rss_kb diff --git a/docs/examples/gcd/ics55flow.py b/docs/examples/gcd/ics55flow.py index 5a8f64f3..6dc82b79 100644 --- a/docs/examples/gcd/ics55flow.py +++ b/docs/examples/gcd/ics55flow.py @@ -1,6 +1,6 @@ -from chipcompiler.data import create_workspace, get_pdk, StepEnum, StateEnum -from chipcompiler.engine import EngineFlow from benchmark import get_parameters +from chipcompiler.data import StateEnum, StepEnum, create_workspace, get_pdk +from chipcompiler.engine import EngineFlow # Setup paths workspace_dir = "./gcd_workspace" @@ -60,7 +60,7 @@ origin_def="", origin_verilog=input_verilog, pdk=pdk, - parameters=parameters + parameters=parameters, ) # Use load_workspace to resume from existing workspace # workspace = load_workspace(directory=workspace_dir) @@ -80,4 +80,4 @@ # Create step workspaces and run engine_flow.create_step_workspaces() -engine_flow.run_steps() \ No newline at end of file +engine_flow.run_steps() diff --git a/docs/examples/gcd/ics55flow_with_filelist.py b/docs/examples/gcd/ics55flow_with_filelist.py index 35d45fbd..ebedeec3 100644 --- a/docs/examples/gcd/ics55flow_with_filelist.py +++ b/docs/examples/gcd/ics55flow_with_filelist.py @@ -13,9 +13,9 @@ docs/specification/filelist-grammar.md """ -from chipcompiler.data import create_workspace, get_pdk, StepEnum, StateEnum -from chipcompiler.engine import EngineFlow from benchmark import get_parameters +from chipcompiler.data import StateEnum, StepEnum, create_workspace, get_pdk +from chipcompiler.engine import EngineFlow # Setup paths workspace_dir = "./gcd_workspace_with_filelist" @@ -84,7 +84,7 @@ origin_verilog="", # Not needed when using filelist pdk=pdk, parameters=parameters, - input_filelist=input_filelist # Provide filelist path here + input_filelist=input_filelist, # Provide filelist path here ) # Use load_workspace to resume from existing workspace # workspace = load_workspace(directory=workspace_dir) diff --git a/pyproject.toml b/pyproject.toml index e9fdd976..9a5a7171 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ chipcompiler = "chipcompiler.services.run_server:main" [tool.ruff] line-length = 100 +extend-exclude = ["chipcompiler/thirdparty/iEDA/**"] [tool.ruff.lint] select = [ diff --git a/test/test_benchmark_inputs.py b/test/test_benchmark_inputs.py index 3cb41b15..2defc918 100644 --- a/test/test_benchmark_inputs.py +++ b/test/test_benchmark_inputs.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- """ Test benchmark input validation and priority logic. @@ -10,15 +9,16 @@ import os import sys +from unittest.mock import MagicMock, patch + import pytest -from unittest.mock import patch, MagicMock current_dir = os.path.dirname(os.path.abspath(__file__)) root = os.path.dirname(current_dir) sys.path.insert(0, root) -from chipcompiler.data import StepEnum -from benchmark.benchmark import run_benchmark, run_single_design +from benchmark.benchmark import run_benchmark, run_single_design # noqa: E402 +from chipcompiler.data import StepEnum # noqa: E402 FIXTURES_DIR = os.path.join(current_dir, "fixtures", "benchmark") @@ -41,9 +41,11 @@ def mock_engine_flow(): def capture_step(step, tool, state): captured_steps.append((step, tool, state)) - with patch("benchmark.benchmark.EngineFlow.run_steps"): - with patch("benchmark.benchmark.EngineFlow.add_step", side_effect=capture_step): - yield captured_steps + with ( + patch("benchmark.benchmark.EngineFlow.run_steps"), + patch("benchmark.benchmark.EngineFlow.add_step", side_effect=capture_step), + ): + yield captured_steps # Valid input files that should pass validation @@ -96,10 +98,10 @@ def _run_and_get_steps(self, json_file: str, mock_engine_flow) -> list: data = json_read(json_path) workspace_dir = f"/tmp/test_{json_file.replace('.json', '')}" - try: + from contextlib import suppress + + with suppress(Exception): run_single_design(workspace_dir, data["pdk"], data["designs"][0]) - except Exception: - pass # Ignore file operation errors return mock_engine_flow diff --git a/test/test_filelist.py b/test/test_filelist.py index f350943a..289f5efe 100644 --- a/test/test_filelist.py +++ b/test/test_filelist.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- """ Test filelist parsing and copying functionality. @@ -13,22 +12,23 @@ import os import sys + import pytest current_dir = os.path.dirname(os.path.abspath(__file__)) root = os.path.dirname(current_dir) sys.path.insert(0, root) -from chipcompiler.utility.filelist import ( +from chipcompiler.data import create_workspace, get_pdk # noqa: E402 +from chipcompiler.data.parameter import Parameters # noqa: E402 +from chipcompiler.data.workspace import copy_filelist_with_sources # noqa: E402 +from chipcompiler.utility.filelist import ( # noqa: E402 + get_filelist_info, parse_filelist, + parse_incdir_directives, resolve_path, validate_filelist, - get_filelist_info, - parse_incdir_directives ) -from chipcompiler.data.workspace import copy_filelist_with_sources -from chipcompiler.data import create_workspace, get_pdk -from chipcompiler.data.parameter import Parameters @pytest.fixture @@ -47,7 +47,7 @@ def test_parameters(): "Design": "test", "Top module": "top", "Clock": "clk", - "Frequency max [MHz]": 100 + "Frequency max [MHz]": 100, } return parameters @@ -98,10 +98,7 @@ def test_parse_with_comments(self, tmp_path): """Parse filelist with comments.""" filelist = tmp_path / "design.f" filelist.write_text( - "# This is a comment\n" - "rtl/top.v\n" - "// Another comment\n" - "rtl/sub.v # inline comment\n" + "# This is a comment\nrtl/top.v\n// Another comment\nrtl/sub.v # inline comment\n" ) assert parse_filelist(str(filelist)) == ["rtl/top.v", "rtl/sub.v"] @@ -116,7 +113,7 @@ def test_parse_with_empty_lines(self, tmp_path): def test_parse_with_quotes(self, tmp_path): """Parse filelist with quoted paths.""" filelist = tmp_path / "design.f" - filelist.write_text('"path with spaces/file.v"\n' "'another path/file.v'\n") + filelist.write_text("\"path with spaces/file.v\"\n'another path/file.v'\n") assert parse_filelist(str(filelist)) == ["path with spaces/file.v", "another path/file.v"] @@ -239,13 +236,13 @@ def test_get_info(self, tmp_path): info = get_filelist_info(str(filelist)) - assert info['filelist'] == os.path.abspath(str(filelist)) - assert info['base_dir'] == str(tmp_path) - assert info['total_files'] == 2 - assert info['existing_files'] == ["rtl/gcd.v"] - assert info['missing_files'] == ["rtl/missing.v"] - assert "rtl/gcd.v" in info['file_sizes'] - assert info['file_sizes']["rtl/gcd.v"] > 0 + assert info["filelist"] == os.path.abspath(str(filelist)) + assert info["base_dir"] == str(tmp_path) + assert info["total_files"] == 2 + assert info["existing_files"] == ["rtl/gcd.v"] + assert info["missing_files"] == ["rtl/missing.v"] + assert "rtl/gcd.v" in info["file_sizes"] + assert info["file_sizes"]["rtl/gcd.v"] > 0 class TestCopyFilelistWithSources: @@ -351,7 +348,7 @@ def test_workspace_with_filelist(self, tmp_path, test_parameters, pdk): origin_verilog="", pdk=pdk, parameters=test_parameters, - input_filelist=str(filelist) + input_filelist=str(filelist), ) assert os.path.exists(workspace_dir) @@ -379,7 +376,7 @@ def test_workspace_with_nested_filelist(self, tmp_path, test_parameters, pdk): origin_verilog="", pdk=pdk, parameters=test_parameters, - input_filelist=str(filelist) + input_filelist=str(filelist), ) origin_dir = workspace_dir / "origin" @@ -403,12 +400,7 @@ def test_parse_single_incdir(self, tmp_path): def test_parse_multiple_incdir(self, tmp_path): """Parse filelist with multiple +incdir directives.""" - content = ( - "+incdir+./include\n" - "+incdir+./rtl/common\n" - "+incdir+../shared/headers\n" - "rtl/top.v\n" - ) + content = "+incdir+./include\n+incdir+./rtl/common\n+incdir+../shared/headers\nrtl/top.v\n" dirs = self._parse_incdir(tmp_path, content) assert dirs == ["./include", "./rtl/common", "../shared/headers"] @@ -420,9 +412,7 @@ def test_parse_incdir_current_dir(self, tmp_path): def test_parse_incdir_with_comments(self, tmp_path): """Parse +incdir directives with inline comments.""" content = ( - "+incdir+./include # Main headers\n" - "+incdir+./rtl/common // Common headers\n" - "rtl/top.v\n" + "+incdir+./include # Main headers\n+incdir+./rtl/common // Common headers\nrtl/top.v\n" ) dirs = self._parse_incdir(tmp_path, content) assert dirs == ["./include", "./rtl/common"] @@ -439,22 +429,18 @@ def test_parse_incdir_empty_filelist(self, tmp_path): def test_parse_incdir_skip_comments(self, tmp_path): """Ensure comments are skipped when parsing +incdir.""" - content = ( - "# +incdir+./should_skip\n" - "// +incdir+./also_skip\n" - "+incdir+./valid\n" - ) + content = "# +incdir+./should_skip\n// +incdir+./also_skip\n+incdir+./valid\n" dirs = self._parse_incdir(tmp_path, content) assert dirs == ["./valid"] def test_parse_incdir_with_spaces(self, tmp_path): """Parse +incdir directives with surrounding spaces.""" content = ( - "+incdir+ ./include\n" # Space after prefix - "+incdir+ ./rtl/common \n" # Multiple spaces - "+incdir+ \"./quoted\" # comment\n" # Spaces with quotes - " +incdir+./leading\n" # Leading spaces on line - "\t+incdir+./tab\n" # Leading tab + "+incdir+ ./include\n" # Space after prefix + "+incdir+ ./rtl/common \n" # Multiple spaces + '+incdir+ "./quoted" # comment\n' # Spaces with quotes + " +incdir+./leading\n" # Leading spaces on line + "\t+incdir+./tab\n" # Leading tab ) dirs = self._parse_incdir(tmp_path, content) assert dirs == ["./include", "./rtl/common", "./quoted", "./leading", "./tab"] @@ -495,9 +481,7 @@ def test_copy_with_incdir_deduplication(self, tmp_path): write_rtl_file(project_dir / "top.v", "top") write_header_file(project_dir / "types.vh", "`define TYPES") - origin_dir = self._copy_and_get_origin( - tmp_path, project_dir, "top.v\n+incdir+./\n" - ) + origin_dir = self._copy_and_get_origin(tmp_path, project_dir, "top.v\n+incdir+./\n") assert (origin_dir / "top.v").exists() assert (origin_dir / "types.vh").exists() diff --git a/test/test_ics55_batch.py b/test/test_ics55_batch.py index 424448f7..180d7c28 100644 --- a/test/test_ics55_batch.py +++ b/test/test_ics55_batch.py @@ -1,56 +1,68 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys import os +import sys current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] +root = current_dir.rsplit("/", 1)[0] sys.path.append(root) -from benchmark import run_benchmark, benchmark_statis, benchmark_metrics +from benchmark import benchmark_metrics, benchmark_statis, run_benchmark # noqa: E402 + def test_benchmark_batch(): benchmark_json = f"{root}/benchmark/ics55_benchmark.json" - + # run batch - run_benchmark(benchmark_json=benchmark_json, - target_dir="/nfs/home/huangzengrong/benchmark", - batch_name="ics55_batch_0") - + run_benchmark( + benchmark_json=benchmark_json, + target_dir="/nfs/home/huangzengrong/benchmark", + batch_name="ics55_batch_0", + ) + benchmark_statis(benchmark_dir="/nfs/home/huangzengrong/benchmark/ics55_batch_0") - benchmark_metrics(benchmark_json=benchmark_json, - target_dir="/nfs/home/huangzengrong/benchmark", - batch_name="ics55_batch_0") - + benchmark_metrics( + benchmark_json=benchmark_json, + target_dir="/nfs/home/huangzengrong/benchmark", + batch_name="ics55_batch_0", + ) + + def test_benchmark_single(): benchmark_json = f"{root}/benchmark/ics55_benchmark.json" - + # run batch - run_benchmark(benchmark_json=benchmark_json, - target_dir="/nfs/home/huangzengrong/benchmark", - batch_name="ics55_batch_0", - design="arm9") - + run_benchmark( + benchmark_json=benchmark_json, + target_dir="/nfs/home/huangzengrong/benchmark", + batch_name="ics55_batch_0", + design="arm9", + ) + + def test_benchmark_tapout(): benchmark_json = f"{root}/benchmark/ics55_tapeout.json" - + # run batch - run_benchmark(benchmark_json=benchmark_json, - target_dir="/nfs/home/huangzengrong/benchmark", - batch_name="ics55_tapeout") - + run_benchmark( + benchmark_json=benchmark_json, + target_dir="/nfs/home/huangzengrong/benchmark", + batch_name="ics55_tapeout", + ) + benchmark_statis(benchmark_dir="/nfs/home/huangzengrong/benchmark/ics55_tapeout") - benchmark_metrics(benchmark_json=benchmark_json, - target_dir="/nfs/home/huangzengrong/benchmark", - batch_name="ics55_tapeout") - - + benchmark_metrics( + benchmark_json=benchmark_json, + target_dir="/nfs/home/huangzengrong/benchmark", + batch_name="ics55_tapeout", + ) + + if __name__ == "__main__": # test_benchmark_batch() - + # test_benchmark_single() - + test_benchmark_tapout() exit(0) diff --git a/test/test_service.py b/test/test_service.py index 20251ea4..6c0d6214 100644 --- a/test/test_service.py +++ b/test/test_service.py @@ -1,71 +1,48 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys import os +import sys current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] +root = current_dir.rsplit("/", 1)[0] sys.path.append(root) -from chipcompiler.data import ( - create_workspace, - log_workspace, - StepEnum, - StateEnum, - get_pdk -) -from chipcompiler.engine import ( - EngineDB, - EngineFlow -) +from benchmark import get_parameters # noqa: E402 +from chipcompiler.services import ECCRequest, ecc_service # noqa: E402 -from chipcompiler.services import ( - ECCService, - ecc_service, - ECCRequest, - ECCResponse -) - -from benchmark import get_parameters def test_sky130_gcd(): - workspace_dir="{}/test/examples/sky130_gcd".format(root) + workspace_dir = f"{root}/test/examples/sky130_gcd" input_def = "" input_verilog = "" - input_filelist = "{}/test/fixtures/gcd/filelist.f".format(root) + input_filelist = f"{root}/test/fixtures/gcd/filelist.f" ecc_serv = ecc_service() - - parameters=get_parameters("sky130", "gcd") - + + parameters = get_parameters("sky130", "gcd") + # create workspace ##################################################### ecc_req = ECCRequest( - cmd = "create_workspace", - data = { - "directory" : workspace_dir, - "pdk" : "sky130", - "parameters" : parameters.data, - "origin_def" : input_def, - "origin_verilog" : input_verilog, - "filelist" : input_filelist - } + cmd="create_workspace", + data={ + "directory": workspace_dir, + "pdk": "sky130", + "parameters": parameters.data, + "origin_def": input_def, + "origin_verilog": input_verilog, + "filelist": input_filelist, + }, ) ecc_response = ecc_serv.create_workspace(ecc_req) - + # load workspace ##################################################### - ecc_req = ECCRequest( - cmd = "load_workspace", - data = { - "directory" : workspace_dir - } - ) + ecc_req = ECCRequest(cmd="load_workspace", data={"directory": workspace_dir}) ecc_response = ecc_serv.load_workspace(ecc_req) print(ecc_response) - + # delete workspace ##################################################### # ecc_req = ECCRequest( @@ -75,7 +52,7 @@ def test_sky130_gcd(): # } # ) # ecc_response = ecc_serv.delete_workspace(ecc_req) - + # run rtl2gds ##################################################### # ecc_req = ECCRequest( @@ -85,38 +62,27 @@ def test_sky130_gcd(): # } # ) # ecc_response = ecc_serv.rtl2gds(ecc_req) - - + # test run single step - ##################################################### + ##################################################### from chipcompiler.rtl2gds import build_rtl2gds_flow + steps = build_rtl2gds_flow() - for step, tool, state in steps: - ecc_req = ECCRequest( - cmd = "run_step", - data = { - "step" : step.value, - "rerun" : False - } - ) + for step, _tool, _state in steps: + ecc_req = ECCRequest(cmd="run_step", data={"step": step.value, "rerun": False}) # ecc_response = ecc_serv.run_step(ecc_req) # print(ecc_response) - + # test get step infomation ##################################################### from chipcompiler.services import InfoEnum - for step, tool, state in steps: - for info_enum in InfoEnum: - ecc_req = ECCRequest( - cmd = "get_info", - data = { - "step" : step.value, - "id" : info_enum.value - } - ) + + for step, _tool, _state in steps: + for info_enum in InfoEnum: + ecc_req = ECCRequest(cmd="get_info", data={"step": step.value, "id": info_enum.value}) ecc_response = ecc_serv.get_info(ecc_req) print(ecc_response) - + print(1) diff --git a/test/test_tools_ieda.py b/test/test_tools_ieda.py index 001d2468..45abe5d8 100644 --- a/test/test_tools_ieda.py +++ b/test/test_tools_ieda.py @@ -1,37 +1,32 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -import sys import os +import sys current_dir = os.path.split(os.path.abspath(__file__))[0] -root = current_dir.rsplit('/', 1)[0] +root = current_dir.rsplit("/", 1)[0] sys.path.append(root) -from chipcompiler.data import ( +from benchmark import get_parameters # noqa: E402 +from chipcompiler.data import ( # noqa: E402 + StateEnum, + StepEnum, create_workspace, + get_pdk, log_workspace, - StepEnum, - StateEnum, - get_pdk -) - -from chipcompiler.engine import ( - EngineDB, - EngineFlow ) +from chipcompiler.engine import EngineFlow # noqa: E402 -from benchmark import get_parameters def test_sky130_gcd(): - workspace_dir="{}/test/examples/sky130_gcd".format(root) - + workspace_dir = f"{root}/test/examples/sky130_gcd" + input_def = "" - # input_verilog = "{}/chipcompiler/thirdparty/ecc-tools/scripts/design/sky130_gcd/result/verilog/gcd.v".format(root) # verilog file + # input_verilog = "{}/chipcompiler/thirdparty/ecc-tools/scripts/design/sky130_gcd/result/verilog/gcd.v".format(root) # verilog file # noqa: E501 input_verilog = "" - input_filelist = "{}/test/fixtures/gcd/filelist.f".format(root) # file list - spef="{}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130/spef/gcd.spef".format(root) - parameters=get_parameters("sky130", "gcd") + input_filelist = f"{root}/test/fixtures/gcd/filelist.f" # file list + spef = f"{root}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130/spef/gcd.spef" + parameters = get_parameters("sky130", "gcd") pdk = get_pdk("sky130") pdk.spef = spef @@ -41,10 +36,10 @@ def test_sky130_gcd(): origin_verilog=input_verilog, pdk=pdk, parameters=parameters, - input_filelist=input_filelist + input_filelist=input_filelist, ) - - # use the origin def and verilog in workspace for the first step. + + # use the origin def and verilog in workspace for the first step. # create eda tool instance engine_flow = EngineFlow(workspace=workspace) if not engine_flow.has_init(): @@ -59,22 +54,22 @@ def test_sky130_gcd(): engine_flow.add_step(step=StepEnum.ROUTING, tool="ecc", state=StateEnum.Unstart) engine_flow.add_step(step=StepEnum.DRC, tool="ecc", state=StateEnum.Unstart) engine_flow.add_step(step=StepEnum.FILLER, tool="ecc", state=StateEnum.Unstart) - + engine_flow.create_step_workspaces() - + log_workspace(workspace=workspace) # engine_flow.init_db_engine() engine_flow.run_steps() - + def test_ics55_gcd(): - workspace_dir="{}/test/examples/ics55_gcd".format(root) + workspace_dir = f"{root}/test/examples/ics55_gcd" input_def = "" - input_verilog = "{}/test/fixtures/benchmark/dummy/gcd.v".format(root) # RTL file - spef="{}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130/spef/gcd.spef".format(root) - parameters=get_parameters("ics55", "gcd") + input_verilog = f"{root}/test/fixtures/benchmark/dummy/gcd.v" # RTL file + spef = f"{root}/chipcompiler/thirdparty/ecc-tools/scripts/foundry/sky130/spef/gcd.spef" + parameters = get_parameters("ics55", "gcd") pdk = get_pdk("ics55") pdk.spef = spef @@ -83,7 +78,7 @@ def test_ics55_gcd(): origin_def=input_def, origin_verilog=input_verilog, pdk=pdk, - parameters=parameters + parameters=parameters, ) engine_flow = EngineFlow(workspace=workspace) @@ -98,14 +93,15 @@ def test_ics55_gcd(): engine_flow.add_step(step=StepEnum.DRC, tool="ecc", state=StateEnum.Unstart) engine_flow.add_step(step=StepEnum.FILLER, tool="ecc", state=StateEnum.Unstart) engine_flow.create_step_workspaces() - + log_workspace(workspace=workspace) - + engine_flow.run_steps() - + + if __name__ == "__main__": test_sky130_gcd() - + # test_ics55_gcd() exit(0)