diff --git a/.github/workflows/gds.yaml b/.github/workflows/gds.yaml index 72045a07..fa89dba2 100644 --- a/.github/workflows/gds.yaml +++ b/.github/workflows/gds.yaml @@ -88,6 +88,11 @@ jobs: python ../sky130_density_fix/replace_decap.py --design $project --user-gds $project/$project.gds --replacement-gds ../sky130_density_fix/sky130_ef_sc_hd__newfill_12.gds done + # Workaround for DRC errors in the SRAM macro of tt_um_kmakise_sram: + - name: Patch tt_um_kmakise_sram for SRAM DRC errors + working-directory: projects/tt_um_kmakise_sram + run: python ../sky130_sram_workaround/extract_sram_macro.py -n tt_um_kmakise_sram -p tt_um_kmakise_sram.gds -m myconfig_sky_dual -x myconfig_sky_dual.gds + # run OpenLane to build the GDS - name: Harden Chip ROM run: nix-shell $GITHUB_WORKSPACE/openlane2/shell.nix --run "python -m openlane tt/rom/config.json" @@ -107,6 +112,13 @@ jobs: working-directory: tt-multiplexer/ol2/tt_top run: nix-shell $GITHUB_WORKSPACE/openlane2/shell.nix --run "python build.py --skip-xor-checks" + - name: Copy sram macro back into tt_um_kmakise_sram + run: | + # Find the latest run directory: + RUN_DIR=$(ls -d tt-multiplexer/ol2/tt_top/runs/RUN_* | sort -n | tail -n 1) + cp $RUN_DIR/final/gds/openframe_project_wrapper.gds $RUN_DIR/final/gds/openframe_project_wrapper.orig.gds + python ../sky130_sram_workaround/inject_sram_macro.py -i $RUN_DIR/final/gds/openframe_project_wrapper.gds -o $RUN_DIR/final/gds/openframe_project_wrapper.gds -p tt_um_kmakise_sram -m myconfig_sky_dual --inject-gds projects/tt_um_kmakise_sram/myconfig_sky_dual.gds + - name: Copy final results run: python ./tt/configure.py --copy-final-results diff --git a/sky130_sram_workaround/extract_sram_macro.py b/sky130_sram_workaround/extract_sram_macro.py new file mode 100644 index 00000000..04a2aa9a --- /dev/null +++ b/sky130_sram_workaround/extract_sram_macro.py @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: Apache-2.0 +# Authors: Uri Shaked + +import klayout.db as pya +import argparse + +parser = argparse.ArgumentParser( + description="Extracts a given macro from a user project into a separate file, removing the original macro content from the user's project." +) + +parser.add_argument("--design-name", "-n", required=True) +parser.add_argument("--project-gds", "-p", required=True) +parser.add_argument("--macro-name", "-m", required=True) +parser.add_argument("--extracted-gds", "-x", required=True) + + +args = parser.parse_args() + +design_name = args.design_name +project_gds = args.project_gds +macro_name = args.macro_name +extracted_gds = args.extracted_gds + +user_layout = pya.Layout() +user_layout.read(project_gds) + +macro_to_extract = user_layout.cell(macro_name) +if macro_to_extract.is_empty(): + raise ValueError(f"Cell {macro_name} is empty.") + +extracted_layout = pya.Layout() +extracted_layout.dbu = user_layout.dbu +extracted_cell = extracted_layout.create_cell(macro_to_extract) +extracted_cell.name = macro_name +extracted_cell.copy_tree(macro_to_extract) + +macro_to_extract.clear() +# Create a single shape in the original cell to avoid opelane error about empty cells during the Stream Out (KLayout) step. +macro_to_extract.shapes(0).insert(pya.Box(0, 0, 0, 0)) + +has_orphan_cells = True +while has_orphan_cells: + has_orphan_cells = False + for cell in user_layout.top_cells(): + if cell.name != design_name: + cell.delete() + has_orphan_cells = True + +print(f"Extracted {macro_name} from {project_gds} to {extracted_gds}.") +user_layout.write(project_gds) +extracted_layout.write(extracted_gds) diff --git a/sky130_sram_workaround/inject_sram_macro.py b/sky130_sram_workaround/inject_sram_macro.py new file mode 100644 index 00000000..f140607c --- /dev/null +++ b/sky130_sram_workaround/inject_sram_macro.py @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: Apache-2.0 +# Authors: Uri Shaked + +import klayout.db as pya +import argparse + +parser = argparse.ArgumentParser(description="Injects the macro into a user's project.") + +parser.add_argument("--input-gds", "-i", required=True) +parser.add_argument("--output-gds", "-o", required=True) +parser.add_argument("--project-name", "-p", required=True) +parser.add_argument("--macro-name", "-m", required=True) +parser.add_argument("--inject-gds", required=True) + +args = parser.parse_args() + +input_gds = args.input_gds +output_gds = args.output_gds +macro_name = args.macro_name +project_name = args.project_name +inject_gds = args.inject_gds + +tt_layout = pya.Layout() +tt_layout.read(input_gds) + +user_project_cell = tt_layout.cell(project_name) +prefix = None +for inst in user_project_cell.each_inst(): + assert inst + prefix = inst.cell.name[:3] + assert prefix[2] == "_" + +assert prefix + +macro_layout = pya.Layout() +macro_layout.read(inject_gds) + +target_cell = tt_layout.cell(prefix + macro_name) +target_cell.clear() +target_cell.copy_tree(macro_layout.cell(macro_name)) + +print(f"Injected {macro_name} and wrote the result into {output_gds}.") +tt_layout.write()