From d7aed0435d8cc37fe712638456251910b15add66 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Wed, 26 Jul 2023 11:27:15 +0300 Subject: [PATCH] Rewrite `dffram.py` to use OpenLane 2 + Nix (#187) --- .github/workflows/main.yml | 23 +- .gitignore | 3 +- Makefile | 17 +- Readme.md | 17 +- dffram.ipynb | 12 +- dffram.py | 1017 ++++++++---------------------- docs/Usage.md | 83 ++- placeram/cli.py | 88 ++- requirements.txt | 4 +- scripts/openlane/interactive.tcl | 54 -- scripts/perl/verilog_to_lib.pl | 210 ------ shell.nix | 23 + 12 files changed, 433 insertions(+), 1118 deletions(-) delete mode 100644 scripts/openlane/interactive.tcl delete mode 100755 scripts/perl/verilog_to_lib.pl create mode 100644 shell.nix diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fff341f..4b0ce9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,21 +91,30 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + - name: Setup Nix + uses: cachix/install-nix-action@v22 + with: + nix_path: nixpkgs=channel:nixos-22.11 + - name: Setup Cachix + uses: cachix/cachix-action@v12 + with: + name: openlane - name: Run run: | - pip3 install --upgrade --no-cache-dir -r ./requirements.txt - building_blocks=ram if [ "${{ matrix.variant }}" == "2R1W" ]; then building_blocks=rf fi - ./dffram.py\ - -s ${{ matrix.count }}x${{ matrix.width }}\ - -b sky130A:sky130_fd_sc_hd:$building_blocks\ - -v ${{ matrix.variant }} + nix-shell --run "\ + ./dffram.py ${{ matrix.count }}x${{ matrix.width }}\ + -p sky130A\ + -s sky130_fd_sc_hd\ + -v ${{ matrix.variant }}\ + -b $building_blocks\ + " - echo "PRODUCTS_PATH=$(cat ./products_path)" >> $GITHUB_ENV + echo "PRODUCTS_PATH=$(echo products/*)" >> $GITHUB_ENV - name: Upload Final Views uses: actions/upload-artifact@v3 with: diff --git a/.gitignore b/.gitignore index 366a62e..c8560a8 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ support/ *.cvcrc *.xz venv/ -/products_path \ No newline at end of file +/products_path +/products \ No newline at end of file diff --git a/Makefile b/Makefile index a4afff9..0596a39 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,25 @@ all: dist .PHONY: dist -dist: venv/created +dist: venv/manifest.txt ./venv/bin/python3 setup.py sdist bdist_wheel .PHONY: lint -lint: venv/created +lint: venv/manifest.txt ./venv/bin/black --check . ./venv/bin/flake8 . -venv: venv/created -venv/created: ./requirements_dev.txt ./requirements.txt +venv: venv/manifest.txt +venv/manifest.txt: ./requirements_dev.txt ./requirements.txt rm -rf venv python3 -m venv ./venv - ./venv/bin/python3 -m pip install --upgrade pip - ./venv/bin/python3 -m pip install --upgrade wheel - ./venv/bin/python3 -m pip install --upgrade\ + PYTHONPATH= ./venv/bin/python3 -m pip install --upgrade pip + PYTHONPATH= ./venv/bin/python3 -m pip install --upgrade wheel + PYTHONPATH= ./venv/bin/python3 -m pip install --upgrade\ -r ./requirements_dev.txt\ -r ./requirements.txt - touch venv/created + PYTHONPATH= ./venv/bin/python3 -m pip freeze > $@ + touch venv/manifest.txt .PHONY: veryclean veryclean: clean diff --git a/Readme.md b/Readme.md index ce85288..e1ceb13 100644 --- a/Readme.md +++ b/Readme.md @@ -11,17 +11,11 @@ Standard Cell Library based Memory Compiler using DFF/Latch cells. -# ✨ Quick Usage -You can try the [Google Colaboratory](https://colab.research.google.com/github/Cloud-V/DFFRAM/blob/main/dffram.ipynb), but also... +# ✨ Installation & Usage +See [this document](./docs/Usage.md). -[Get **Docker**](https://docs.docker.com/get-docker/), set it up nicely, then, in your terminal: - -```sh -pip3 install --user --no-cache-dir --upgrade ./requirements.txt -./dffram.py -s 32x32 -``` - -If all goes well, you've placed your first register file! +You can try the [Google Colaboratory](https://colab.research.google.com/github/Cloud-V/DFFRAM/blob/main/dffram.ipynb), +but it is a bit out-of-date at this point. # Overview The objective of this project is to develop a DFF/Latch-based RAM, Register File and Cache custom compilation flow that utilizes standard cell libraries following a standard ASIC (Application Specific Integrated Circuit) implementation approach. Different views (HDL netlist, HDL functional models, LEF, GDS, Timing, …) are all generated for a given size configuration. @@ -142,7 +136,8 @@ The following table compares the areas and bit densities of RAM macros generated # ⚖️ Copyright and Licensing -Copyright ©2020-2022 The American University in Cairo + +Copyright ©2020-2023 The American University in Cairo Licensed under the Apache License, Version 2.0 (the "Open Source License"); you may not use this file except in compliance with the Open Source License. diff --git a/dffram.ipynb b/dffram.ipynb index 8704ac9..2241fdd 100644 --- a/dffram.ipynb +++ b/dffram.ipynb @@ -6,13 +6,15 @@ "id": "eukW5KG9kq7A" }, "source": [ - "# DFFRAM Compiler\n", + "# DFFRAM Compiler\n", "\n", + "Standard Cell Library based Memory Compiler using DFF/Latch cells.\n", "\n", - " Standard Cell Library based Memory Compiler using DFF/Latch cells.\n", "\n", + "## Warning!\n", "\n", - "\n" + "This notebook should still work, but is out of date and does not reflect the\n", + "latest DFFRAM release. Use at your own risk!" ] }, { @@ -325,7 +327,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3.10.9 64-bit", + "display_name": "Python 3.10.9 ('venv': venv)", "language": "python", "name": "python3" }, @@ -343,7 +345,7 @@ }, "vscode": { "interpreter": { - "hash": "3fe098a60de41da4d418149dd85333239847aa92272c8f79d3c6f03653219927" + "hash": "192516fa94e535c4f5cf27eeef389e5df5ced4f52a77092f31776bfbcc9cff03" } } }, diff --git a/dffram.py b/dffram.py index 7a45005..e4f0a61 100755 --- a/dffram.py +++ b/dffram.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf8 -*- -# Copyright ©2020-2022 The American University in Cairo +# Copyright ©2020-2023 The American University in Cairo +# Copyright ©2023 Efabless Corporation # # This file is part of the DFFRAM Memory Compiler. # See https://github.com/Cloud-V/DFFRAM for further info. @@ -17,637 +18,277 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -import sys import re -import uuid import math -import time -import shutil -import fnmatch -import pathlib -import traceback -import subprocess - - -def eprint(*args, **kwargs): - print(*args, **kwargs, file=sys.stderr) - - -try: - import click - import yaml - import volare -except ImportError as e: - eprint(e) - eprint("---") - eprint( - "You need to install dependencies: pip3 install --user --upgrade --no-cache-dir -r ./requirements.txt" - ) - exit(os.EX_CONFIG) - - -def rp(path): - return os.path.realpath(path) - - -def ensure_dir(path): - return pathlib.Path(path).mkdir(parents=True, exist_ok=True) - - -# -- -build_folder = "" -pdk_family = "" -pdk = "" -pdk_version = "" -scl = "" -pdk_root = "" -pdk_tech_dir = "" -pdk_ref_dir = "" -pdk_liberty_dir = "" -pdk_lef_dir = "" -pdk_tlef_dir = "" -pdk_klayout_dir = "" -pdk_magic_dir = "" -pdk_openlane_dir = "" - -tool_metadata_file_path = os.path.join(os.path.dirname(__file__), "tool_metadata.yml") -tool_metadata = yaml.safe_load(open(tool_metadata_file_path).read()) -openlane_version = [tool for tool in tool_metadata if tool["name"] == "openlane"][0][ - "commit" -] -openlane_image = os.getenv( - "OPENLANE_IMAGE_NAME", default=f"efabless/openlane:{openlane_version}" -) - -running_docker_ids = set() - - -def run_docker(image, args): - global running_docker_ids - global command_list - container_id = str(uuid.uuid4()) - running_docker_ids.add(container_id) - cmd = ( - [ - "docker", - "run", - "--rm", - "--name", - container_id, - "-v", - f"{pdk_root}:{pdk_root}", - "-v", - f"{rp('.')}:/mnt/dffram", - "-w", - "/mnt/dffram", - "-e", - f"PDK_ROOT={pdk_root}", - "-e", - f"PDKPATH={pdk_root}/{pdk}", - "-e", - f"PDK={pdk}", - "-e", - "PWD=/mnt/dffram", - "-e", - "LC_ALL=en_US.UTF-8", - "-e", - "LANG=en_US.UTF-8", - ] - + [image] - + args - ) - command_list.append(cmd) - subprocess.check_call(cmd, stdout=sys.stderr, stderr=subprocess.STDOUT) - running_docker_ids.remove(container_id) - - -openlane_scripts_path = "/openlane/scripts" -local_openlane_path = None -venv_lib_path = None - - -def openlane(*args_tuple): - global no_docker_option - args = list(args_tuple) - if local_openlane_path is not None: - env = os.environ.copy() - env["PATH"] = f"{local_openlane_path}:{env['PATH']}" - - if venv_lib_path is not None: - env["PYTHONPATH"] = venv_lib_path - - # Disable tools not typically installed in Colaboratories - env["RUN_KLAYOUT"] = "0" - env["RUN_CVC"] = "0" - env["PDK_ROOT"] = pdk_root - - subprocess.check_call( - args, env=env, stdout=sys.stderr, stderr=subprocess.STDOUT - ) - else: - run_docker(openlane_image, args) - - -def prep(local_pdk_root): - global pdk, scl - global pdk_root, pdk_tech_dir, pdk_ref_dir - global pdk_liberty_dir, pdk_lef_dir, pdk_tlef_dir - global pdk_klayout_dir, pdk_magic_dir, pdk_openlane_dir - pdk_root = os.path.abspath(local_pdk_root) - pdk_path = os.path.join(pdk_root, pdk) - volare.enable(pdk_root=local_pdk_root, pdk=pdk_family, version=pdk_version) - - pdk_tech_dir = os.path.join(pdk_path, "libs.tech") - pdk_ref_dir = os.path.join(pdk_path, "libs.ref") - - pdk_liberty_dir = os.path.join(pdk_ref_dir, scl, "lib") - if not os.path.exists(pdk_liberty_dir): - pdk_liberty_dir = os.path.join(pdk_ref_dir, scl, "liberty") - - pdk_lef_dir = os.path.join(pdk_ref_dir, scl, "lef") - pdk_tlef_dir = os.path.join(pdk_ref_dir, scl, "techlef") - pdk_openlane_dir = os.path.join(pdk_tech_dir, "openlane") - pdk_klayout_dir = os.path.join(pdk_tech_dir, "klayout") - pdk_magic_dir = os.path.join(pdk_tech_dir, "magic") - openlane( - "openroad", - "-exit", - "-python", - f"{openlane_scripts_path}/mergeLef.py", - "-i", - f"{pdk_tlef_dir}/{scl}__nom.tlef", - f"{pdk_lef_dir}/{scl}.lef", - "-o", - f"{build_folder}/merged.lef", - ) - - -command_list = [] - - -def cl(): - with open("./command_list.log", "w") as f: - f.write("\n".join([" ".join(cmd) for cmd in command_list])) +from typing import List +from fnmatch import fnmatch +from decimal import Decimal + +import yaml +import cloup +from openlane.common import mkdirp +from openlane.config import Variable +from openlane.logging import warn, err +from openlane.state import DesignFormat +from openlane.flows import SequentialFlow, cloup_flow_opts, Flow +from openlane.steps import Yosys, OpenROAD, Magic, KLayout, Netgen, Odb, Checker, Misc + + +class PlaceRAM(Odb.OdbpyStep): + id = "DFFRAM.PlaceRAM" + + config_vars = [ + Variable( + "RAM_SIZE", + str, + "The size of the RAM macro being hardened the format {words}x{bits}", + ), + Variable( + "BUILDING_BLOCKS", + str, + "The set of building blocks being used.", + default="ram", + ), + ] + def get_script_path(self): + return "placeram" -# Not true synthesis, just elaboration. -def synthesis( - design, - building_blocks, - block_deinitions, - sta_info, - widths_supported, - word_width_bytes, - out_file, - word_width, - blocks, -): - eprint("--- Synthesis ---") - chparam = "" - if len(widths_supported) > 1: - if blocks == "rf": - chparam = "catch { chparam -set WSIZE %i %s }" % (word_width, design) - else: - chparam = "catch { chparam -set WSIZE %i %s }" % (word_width_bytes, design) - with open(f"{build_folder}/synth.tcl", "w") as f: - f.write( - f""" - yosys -import - set SCL {pdk_liberty_dir}/{sta_info["libs"]["typical"]} - read_liberty -lib -ignore_miss_dir -setattr blackbox $SCL - read_verilog {block_deinitions} - read_verilog {building_blocks} - {chparam} - hierarchy -check -top {design} - synth -flatten - yosys rename -top {design} - opt_clean -purge - splitnets - opt_clean -purge - write_verilog -noattr -noexpr -nodec {out_file} - stat -top {design} -liberty $SCL - exit - """ - ) - - openlane("yosys", f"{build_folder}/synth.tcl") + def get_command(self) -> List[str]: + raw = super().get_command() + [ + "--building-blocks", + f"{self.config['PDK']}:{self.config['STD_CELL_LIBRARY']}:{self.config['BUILDING_BLOCKS']}", + "--size", + self.config["RAM_SIZE"], + ] + raw.insert(raw.index("placeram"), "-m") + return raw -full_width = 0 -full_height = 0 +class Floorplan(OpenROAD.Floorplan): + id = "DFFRAM.Floorplan" + outputs = [ + DesignFormat.ODB, + ] -def floorplan( - design, - sta_info, - wmargin, - hmargin, - width, - height, - in_file, - out_file, - min_height, - min_height_flag, - site_height, - site_name, - tie_lo_cell, - tie_lo_port, - tie_hi_cell, - tie_hi_port, -): - global full_width, full_height - eprint("--- Floorplan ---") - full_width = width + (wmargin * 2) - full_height = height + (hmargin * 2) - - wpm = width + wmargin - hpm = height + hmargin - - track_file = f"{build_folder}/tracks.tcl" - - if min_height_flag: - full_width = width + (wmargin * 2) - full_height = min_height + (hmargin * 2) - hmargin = full_height / 2 - height / 2 - hmargin = math.ceil(hmargin / site_height) * site_height - hpm = height + hmargin - - with open(f"{build_folder}/fp_init.tcl", "w") as f: - f.write( - f""" - read_liberty {pdk_liberty_dir}/{sta_info["libs"]["typical"]} - read_lef {build_folder}/merged.lef - read_verilog {in_file} - link_design {design} - initialize_floorplan\\ - -die_area "0 0 {full_width} {full_height}"\\ - -core_area "{wmargin} {hmargin} {wpm} {hpm}"\\ - -site {site_name} - set tielo_cell {tie_lo_cell} - set tielo_port {tie_lo_port} - set tiehi_cell {tie_hi_cell} - set tiehi_port {tie_hi_port} - insert_tiecells "$tielo_cell/$tielo_port" -prefix "TIE_ZERO_" - insert_tiecells "$tiehi_cell/$tiehi_port" -prefix "TIE_ONE_" - source {build_folder}/tracks.tcl - write_db {out_file} - """ - ) + config_vars = [ + var + for var in OpenROAD.Floorplan.config_vars + if var.name not in ["FP_SIZING", "CORE_AREA", "DIE_AREA"] + ] + [ + Variable( + "HORIZONTAL_HALO", + type=Decimal, + description="The space between the horizontal edges of the die area and the core area in microns.", + units="µm", + default=2.5, + ), + Variable( + "VERTICAL_HALO", + type=Decimal, + description="The space between the vertical edges of the die area and the core area in microns.", + units="µm", + default=2.5, + ), + Variable( + "MINIMUM_HEIGHT", + type=Decimal, + description="A minimum height to be applied", + default=0, + units="µm", + ), + ] - with open(f"{build_folder}/fp_init.sh", "w") as f: - f.write( - f""" - set -e + def run(self, state_in, **kwargs): + min_height = self.config["MINIMUM_HEIGHT"] - python3 {openlane_scripts_path}/new_tracks.py -i {pdk_openlane_dir}/{scl}/tracks.info -o {track_file} - openroad -exit {build_folder}/fp_init.tcl - """ + core_width = Decimal( + state_in.metrics.get("dffram__suggested__core_width") or 20000 ) - - openlane("bash", f"{build_folder}/fp_init.sh") - - -def placeram( - in_file, - out_file, - size, - building_blocks, - dimensions=os.devnull, - density=os.devnull, - represent=os.devnull, -): - eprint("--- placeRAM Script ---") - openlane( - "openroad", - "-exit", - "-python", - "-m", - "placeram", - "--output", - out_file, - "--size", - size, - "--write-dimensions", - dimensions, - "--write-density", - density, - "--represent", - represent, - "--building-blocks", - building_blocks, - in_file, - ) - - -def place_pins(design, sta_info, in_file, out_file, pin_order_file, metal_layer): - eprint("--- Pin Placement ---") - openlane( - "openroad", - "-exit", - "-python", - f"{openlane_scripts_path}/odbpy/io_place.py", - "--config", - pin_order_file, - "--input-lef", - f"{build_folder}/merged.lef", - "--hor-layer", - f"{metal_layer['hor-layer']}", - "--ver-layer", - f"{metal_layer['ver-layer']}", - "--ver-width-mult", - "2", - "--hor-width-mult", - "2", - "--hor-extension", - "0", - "--ver-extension", - "0", - "--length", - "2", - "-o", - out_file, - in_file, - ) - - -def verify_placement(design, sta_info, in_file): - eprint("--- Verify ---") - with open(f"{build_folder}/verify.tcl", "w") as f: - f.write( - f""" - read_db {in_file} - read_liberty {pdk_liberty_dir}/{sta_info["libs"]["typical"]} - if {{[catch check_placement -verbose]}} {{ - puts "Placement failed: Check placement returned a nonzero value." - exit 65 - }} - puts "Placement successful." - """ + core_height = Decimal( + state_in.metrics.get("dffram__suggested__core_height") or 20000 ) - openlane("openroad", "-exit", f"{build_folder}/verify.tcl") - - -def openlane_harden( - design, - clock_period, - final_netlist, - final_placement, - products_path, - sta_info, - routing_threads, - metal_layer, -): - eprint("--- Hardening With OpenLane ---") - design_ol_dir = f"{build_folder}/openlane" - ensure_dir(design_ol_dir) - - netlist_basename = os.path.basename(final_netlist) - current_netlist = f"{design_ol_dir}/{netlist_basename}" - shutil.copy(final_netlist, current_netlist) - - placement_basename = os.path.basename(final_placement) - current_odb = f"{design_ol_dir}/{placement_basename}" - shutil.copy(final_placement, current_odb) - - shutil.copy( - "./scripts/openlane/interactive.tcl", f"{design_ol_dir}/interactive.tcl" - ) - - shutil.copy(f"{pdk_lef_dir}/{scl}.lef", f"{design_ol_dir}/cells.lef") - - with open(f"{design_ol_dir}/config.tcl", "w") as f: - f.write( - f""" - set ::env(DESIGN_NAME) "{design}" - - set ::env(CLOCK_PORT) "CLK" - set ::env(CLOCK_PERIOD) "{clock_period}" - - set ::env(LEC_ENABLE) "0" - set ::env(FP_WELLTAP_CELL) "gf180mcu_fd_sc_mcu7t5v0__filltie*" - - set ::env(GPL_CELL_PADDING) "0" - set ::env(DPL_CELL_PADDING) "0" - set ::env(RUN_FILL_INSERTION) "0" - set ::env(PL_RESIZER_DESIGN_OPTIMIZATIONS) "0" - set ::env(PL_RESIZER_TIMING_OPTIMIZATIONS) "0" - set ::env(GLB_RESIZER_DESIGN_OPTIMIZATIONS) "0" - set ::env(GLB_RESIZER_TIMING_OPTIMIZATIONS) "0" - - set ::env(RT_MAX_LAYER) "{metal_layer['rt-max-layer']}" - set ::env(GRT_ALLOW_CONGESTION) "1" - - set ::env(CELLS_LEF) "$::env(DESIGN_DIR)/cells.lef" - - set ::env(DIE_AREA) "0 0 {full_width} {full_height}" - - set ::env(DIODE_INSERTION_STRATEGY) "0" - set ::env(ROUTING_CORES) {routing_threads} + horizontal_halo = self.config["HORIZONTAL_HALO"] + vertical_halo = self.config["VERTICAL_HALO"] - set ::env(DESIGN_IS_CORE) "0" - set ::env(FP_PDN_CORE_RING) "0" + pdk = self.config["PDK"] + scl = self.config["STD_CELL_LIBRARY"] - set ::env(PRODUCTS_PATH) "{products_path}" + tech_info_path = os.path.join(".", "platforms", pdk, scl, "tech.yml") + tech_info = yaml.safe_load(open(tech_info_path)) + site_info = tech_info.get("site") - set ::env(INITIAL_NETLIST) "$::env(DESIGN_DIR)/{netlist_basename}" - set ::env(INITIAL_ODB) "$::env(DESIGN_DIR)/{placement_basename}" - set ::env(INITIAL_SDC) "$::env(BASE_SDC_FILE)" + site_width = Decimal(1) + site_height = Decimal(1) - set ::env(LVS_CONNECT_BY_LABEL) "1" + if site_info is not None: + site_width = Decimal(site_info["width"]) + site_height = Decimal(site_info["height"]) - set ::env(SYNTH_DRIVING_CELL) "{sta_info["driving_cell"]["name"]}" - set ::env(SYNTH_DRIVING_CELL_PIN) "{sta_info["driving_cell"]["pin"]}" - set ::env(IO_PCT) "0.25" - - """ - ) - - openlane( - "flow.tcl", - "-design", - design_ol_dir, - "-it", - "-file", - f"{design_ol_dir}/interactive.tcl", - ) + horizontal_halo = math.ceil(horizontal_halo / site_width) * site_width + vertical_halo = math.ceil(vertical_halo / site_height) * site_height + else: + if horizontal_halo != 0.0 or vertical_halo != 0.0: + warn( + "Note: This platform does not have site information. The halo will not be rounded up to the nearest number of sites. This may cause off-by-one issues with some tools." + ) + + die_width = core_width + horizontal_halo * 2 + die_height = core_height + vertical_halo * 2 + if die_height < min_height: + die_height = min_height + vertical_halo = (die_height - core_height) / 2 + vertical_halo = math.ceil(vertical_halo / site_height) * site_height + + kwargs, env = self.extract_env(kwargs) + + env["DIE_AREA"] = f"0 0 {die_width} {die_height}" + env[ + "CORE_AREA" + ] = f"{horizontal_halo} {vertical_halo} {horizontal_halo + core_width} {vertical_halo + core_height}" + env["FP_SIZING"] = "absolute" + return super().run(state_in, env=env, **kwargs) + + +@Flow.factory.register() +class DFFRAM(SequentialFlow): + Steps = [ + Yosys.Synthesis, + Misc.LoadBaseSDC, + OpenROAD.STAPrePNR, + Floorplan, + PlaceRAM, + Floorplan, + PlaceRAM, + OpenROAD.IOPlacement, + Odb.CustomIOPlacement, + OpenROAD.GeneratePDN, + OpenROAD.STAMidPNR, + OpenROAD.GlobalRouting, + OpenROAD.STAMidPNR, + OpenROAD.DetailedRouting, + Checker.TrDRC, + Odb.ReportDisconnectedPins, + Checker.DisconnectedPins, + Odb.ReportWireLength, + Checker.WireLength, + OpenROAD.RCX, + OpenROAD.STAPostPNR, + OpenROAD.IRDropReport, + Magic.StreamOut, + Magic.WriteLEF, + KLayout.StreamOut, + KLayout.XOR, + Checker.XOR, + Magic.DRC, + Checker.MagicDRC, + Magic.SpiceExtraction, + Checker.IllegalOverlap, + Netgen.LVS, + Checker.LVS, + ] -@click.command() -# Execution Flow -@click.option("-f", "--from", "frm", default="synthesis", help="Start from this step") -@click.option("-t", "--to", default="gds", help="End after this step") -@click.option( - "--only", default=None, help="Only execute these semicolon;delimited;steps" -) -@click.option("--skip", default=None, help="Skip these semicolon;delimited;steps") - -# Configuration -@click.option( - "-p", - "--pdk-root", - required=False, - default="./pdks", - help="Optionally override the used PDK root", -) -@click.option("-O", "--output-dir", default="./build", help="Output directory.") -@click.option( - "-b", - "--building-blocks", - default="sky130A:sky130_fd_sc_hd:ram", - help="Format {pdk}:{scl}:{name} : ID of the building blocks to use.", -) -@click.option( +@cloup.command() +@cloup.option("-b", "--building-blocks", default="ram") +@cloup.option( "-v", "--variant", default=None, help="Use design variants (such as 1RW1R)" ) -@click.option("-s", "--size", required=True, help="Size") -@click.option( +@cloup.option( "-C", "--clock-period", "default_clock_period", default=20, - type=float, - help="Fallback clock period for STA (when unspecified)", + type=Decimal, + help="Fallback clock period for STA (when unspecified by the platform)", ) -@click.option("--halo", default=2.5, type=float, help="Halo in microns") -@click.option( +@cloup.option( "--horizontal-halo", - default=0.0, - type=float, - help="Horizontal halo in microns (overrides generic halo)", + default=2.5, + type=Decimal, + help="Horizontal halo in µm", ) -@click.option( +@cloup.option( "--vertical-halo", - default=0.0, - type=float, - help="Vertical halo in microns (overrides generic halo)", -) -@click.option( - "-H", "--min-height", default=0.0, type=float, help="Die Area Height in microns" -) -@click.option( - "-j", - "--routing-threads", - type=int, - default=int(os.getenv("ROUTING_CORES") or "1"), - help="Number of threads to be used in routing", -) - -# Enable/Disable -@click.option( - "--klayout/--no-klayout", - default=False, - help="Open the last def in Klayout. (Default: False)", + default=2.5, + type=Decimal, + help="Vertical halo in µm", ) -@click.option( - "--using-local-openlane", - default=None, - type=str, - help="Use this local OpenLane installation instead of a Dockerized installation.", +@cloup.option( + "-H", + "--min-height", + default=0.0, + type=Decimal, + help="Minimum height in µm", ) -def flow( +@cloup_flow_opts(accept_config_files=False) +@cloup.argument("size", default="32x32", nargs=1) +def main( + pdk, + scl, frm, to, - only, - pdk_root, skip, + tag, + last_run, + with_initial_state, size, building_blocks, - default_clock_period, - halo, + variant, horizontal_halo, vertical_halo, - variant, - routing_threads, - klayout, - output_dir, + default_clock_period, min_height, - using_local_openlane, + flow_name, + **kwargs, ): - global build_folder - global pdk_family, pdk, pdk_version, scl - global local_openlane_path - global openlane_scripts_path - global venv_lib_path - - if horizontal_halo == 0.0: - horizontal_halo = halo - - if vertical_halo == 0.0: - vertical_halo = halo - if variant == "DEFAULT": variant = None - local_openlane_path = using_local_openlane - if local_openlane_path is not None: - openlane_scripts_path = os.path.join(local_openlane_path, "scripts") - if not os.getenv("NO_CHECK_INSTALL") == "1": - install_path = os.path.join(local_openlane_path, "install") - if not os.path.isdir(install_path): - eprint(f"Error: OpenLane installation not found at {install_path}.") - exit(os.EX_CONFIG) - - venv_lib = f"{local_openlane_path}/install/venv/lib" - venv_lib_vers = os.listdir(venv_lib) - if len(venv_lib_vers) < 1: - eprint("Installation venv contains no packages.") - exit(os.EX_CONFIG) - - venv_lib_path = os.path.join(venv_lib, venv_lib_vers[0], "site-packages") - - pdk, scl, blocks = building_blocks.split(":") - pdk = pdk or "sky130A" scl = scl or "sky130_fd_sc_hd" - blocks = blocks or "ram" platform = f"{pdk}:{scl}" - building_blocks = f"{platform}:{blocks}" - process_data_file = os.path.join(".", "platforms", pdk, "process_data.yml") - process_data = yaml.safe_load(open(process_data_file)) - pdk_family = process_data["volare_pdk_family"] - pdk_version = process_data["volare_pdk_version"] - - bb_dir = os.path.join(".", "models", blocks) + bb_dir = os.path.join(".", "models", building_blocks) if not os.path.isdir(bb_dir): - eprint(f"Generic building blocks {blocks} not found.") + err(f"Generic building blocks {building_blocks} not found.") exit(os.EX_NOINPUT) pdk_dir = os.path.join(".", "platforms", pdk, scl) if not os.path.isdir(pdk_dir): - eprint(f"Definitions for platform {platform} not found.") + err(f"Definitions for platform {platform} not found.") exit(os.EX_NOINPUT) block_definitions_used = os.path.join(pdk_dir, "block_definitions.v") - bb_used = os.path.join(bb_dir, "model.v") - config_file = os.path.join(bb_dir, "config.yml") - config = yaml.safe_load(open(config_file)) + platform_config_file = os.path.join(bb_dir, "config.yml") + platform_config = yaml.safe_load(open(platform_config_file)) pin_order_file = os.path.join(bb_dir, "pin_order.cfg") - m = re.match(r"(\d+)x(\d+)", size) if m is None: - eprint("Invalid RAM size '%s'." % size) + err(f"Invalid RAM size '{size}'.") exit(os.EX_USAGE) words = int(m[1]) word_width = int(m[2]) - word_width_bytes = word_width / 8 - - if os.getenv("FORCE_ACCEPT_SIZE") is None: - if words not in config["counts"] or word_width not in config["widths"]: - eprint("Size %s not supported by %s." % (size, building_blocks)) + word_width_bytes = word_width // 8 + + if os.getenv("FORCE_ACCEPT_SIZE") != 1: + if ( + words not in platform_config["counts"] + or word_width not in platform_config["widths"] + ): + err("Size %s not supported by %s." % (size, building_blocks)) exit(os.EX_USAGE) - if variant not in config["variants"]: - eprint("Variant %s is unsupported by %s." % (variant, building_blocks)) + if variant not in platform_config["variants"]: + err("Variant %s is unsupported by %s." % (variant, building_blocks)) exit(os.EX_USAGE) - wmargin, hmargin = (horizontal_halo, vertical_halo) # Microns - variant_string = ("_%s" % variant) if variant is not None else "" - design_name_template = config["design_name_template"] + design_name_template = platform_config["design_name_template"] design = os.getenv("FORCE_DESIGN_NAME") or design_name_template.format( **{ "count": words, @@ -656,209 +297,81 @@ def flow( "variant": variant_string, } ) - build_folder = f"{output_dir}/{size}_{variant or 'DEFAULT'}" + + build_dir = os.path.join("build", design) + mkdirp(build_dir) tech_info_path = os.path.join(".", "platforms", pdk, scl, "tech.yml") tech_info = yaml.safe_load(open(tech_info_path)) clock_period = default_clock_period - block_clock_periods = tech_info["sta"]["clock_periods"].get(blocks) + block_clock_periods = tech_info["sta"]["clock_periods"].get(building_blocks) if block_clock_periods is not None: for wildcard, period in block_clock_periods.items(): - if fnmatch.fnmatch(size, wildcard): + if fnmatch(size, wildcard): clock_period = period break - site_info = tech_info.get("site") # Normalize margins in terms of minimum units - if site_info is not None: - site_width = site_info["width"] - site_height = site_info["height"] - site_name = site_info["name"] - - wmargin = math.ceil(wmargin / site_width) * site_width - hmargin = math.ceil(hmargin / site_height) * site_height - else: - if horizontal_halo != 0.0 or vertical_halo != 0.0: - eprint( - "Note: This platform does not have site information. The halo will not be rounded up to the nearest number of sites. This may cause off-by-one issues with some tools." - ) - - sta_info = tech_info.get("sta") - - tie_info = tech_info.get("tie") - - if tie_info is not None: - tie_lo_cell = tie_info["tie_lo_cell"] - tie_lo_port = tie_info["tie_lo_port"] - tie_hi_cell = tie_info["tie_hi_cell"] - tie_hi_port = tie_info["tie_hi_port"] - - metal_layer = tech_info.get("metal_layers") - - ensure_dir(build_folder) - - def i(ext=""): - return f"{build_folder}/{design}{ext}" - - prep(pdk_root) - - start = time.time() - - netlist = i(".nl.v") - dimensions_file = i(".dimensions.txt") - density_file = i(".density.txt") - initial_floorplan = i(".initfp.odb") - initial_placement = i(".initp.odb") - final_floorplan = i(".fp.odb") - no_pins_placement = i(".npp.odb") - final_placement = i(".placed.odb") - - products = f"{build_folder}/products" - - ensure_dir(products) - - width, height = 20000, 20000 + logical_width = word_width_bytes + if building_blocks == "rf": + logical_width = word_width + + TargetFlow = Flow.factory.get(flow_name) or DFFRAM + dffram_flow = TargetFlow( + { + "DESIGN_NAME": design, + "CLOCK_PORT": "CLK", + "CLOCK_PERIOD": clock_period, + "GPL_CELL_PADDING": 0, + "DPL_CELL_PADDING": 0, + "RT_MAX_LAYER": "met4", + "GRT_ALLOW_CONGESTION": True, + "PDK": pdk, + "STD_CELL_LIBRARY": scl, + "RAM_SIZE": size, + "BUILDING_BLOCKS": building_blocks, + "VERILOG_FILES": [ + block_definitions_used, + bb_used, + ], + "SYNTH_ELABORATE_ONLY": True, + "SYNTH_ELABORATE_FLATTEN": True, + "SYNTH_READ_BLACKBOX_LIB": True, + "SYNTH_EXCLUSION_CELL_LIST": "/dev/null", + "SYNTH_PARAMETERS": f"WSIZE={logical_width}", + "GRT_REPAIR_ANTENNAS": False, + "MINIMUM_HEIGHT": min_height, + "VERTICAL_HALO": vertical_halo, + "HORIZONTAL_HALO": horizontal_halo, + "CLOCK_PERIOD": clock_period, + # IO Placement + "FP_PIN_ORDER_CFG": pin_order_file, + "FP_IO_VTHICKNESS_MULT": Decimal(2), + "FP_IO_HTHICKNESS_MULT": Decimal(2), + "FP_IO_HEXTEND": Decimal(0), + "FP_IO_VEXTEND": Decimal(0), + "FP_IO_VLENGTH": 2, + "FP_IO_HLENGTH": 2, + }, + design_dir=build_dir, + ) - def placement(in_width, in_height): - nonlocal width, height, hmargin, wmargin - min_height_flag = False - floorplan( - design, - sta_info, - wmargin, - hmargin, - in_width, - in_height, - netlist, - initial_floorplan, - min_height, - min_height_flag, - site_height, - site_name, - tie_lo_cell, - tie_lo_port, - tie_hi_cell, - tie_hi_port, - ) - placeram( - initial_floorplan, - initial_placement, - size, - building_blocks, - dimensions=dimensions_file, - ) - width, height = map(lambda x: float(x), open(dimensions_file).read().split("x")) - if height < min_height: - min_height_flag = True + final_state = dffram_flow.start( + frm=frm, + to=to, + skip=skip, + tag=tag, + last_run=last_run, + with_initial_state=with_initial_state, + ) - floorplan( + mkdirp("products") + final_state.save_snapshot( + os.path.join( + "products", design, - sta_info, - wmargin, - hmargin, - width, - height, - netlist, - final_floorplan, - min_height, - min_height_flag, - site_height, - site_name, - tie_lo_cell, - tie_lo_port, - tie_hi_cell, - tie_hi_port, ) - placeram( - final_floorplan, - no_pins_placement, - size, - building_blocks, - density=density_file, - ) - place_pins( - design, - sta_info, - no_pins_placement, - final_placement, - pin_order_file, - metal_layer, - ) - verify_placement(design, sta_info, final_placement) - - steps = [ - ( - "synthesis", - lambda: synthesis( - design, - bb_used, - block_definitions_used, - sta_info, - config["widths"], - word_width_bytes, - netlist, - word_width, - blocks, - ), - ), - ("placement", lambda: placement(width, height)), - ( - "openlane_harden", - lambda: openlane_harden( - design, - clock_period, - netlist, - final_placement, - products, - sta_info, - routing_threads, - metal_layer, - ), - ), - ] - - only = only.split(";") if only is not None else None - skip = skip.split(";") if skip is not None else [] - - execute_steps = False - for step in steps: - name, action = step - if frm == name: - execute_steps = True - if execute_steps: - if (only is None or name in only) and (name not in skip): - try: - action() - except KeyboardInterrupt as e: - eprint("\n\nStopping on keyboard interrupt...") - eprint("Killing docker containers...") - for id in running_docker_ids: - subprocess.call(["docker", "kill", id], stdout=os.devnull) - raise e - if to == name: - execute_steps = False - - elapsed = time.time() - start - - eprint("Done in %.2fs." % elapsed) - cl() - - with open("./products_path", "w") as f: - f.write(products) - - -def main(): - try: - flow() - except subprocess.CalledProcessError as e: - eprint("A step has failed:", e) - eprint(f"Quick invoke: {' '.join(e.cmd)}") - cl() - exit(os.EX_UNAVAILABLE) - except Exception: - eprint("An unhandled exception has occurred.", traceback.format_exc()) - cl() - exit(os.EX_UNAVAILABLE) + ) if __name__ == "__main__": diff --git a/docs/Usage.md b/docs/Usage.md index 6dcf1c0..854a9e3 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -1,40 +1,84 @@ # Using DFFRAM -DFFRAM is based around two Python modules: `dffram` and `placeram`. +DFFRAM is based around `placeram`, a Python module, and `dffram.py`, a Python +application. -`dffram.py` is a relatively self-contained flow that uses Openlane and other technologies to place, route and harden RAM. `dffram.py` runs on the host machine. +`placeram` is a custom placer using OpenROAD's Python interface. +It places DFFRAM RAM/RF designs in a predetermined structure to avoid a lengthy +and inefficient manual placement process for RAM. -`placeram` is a custom placer using OpenROAD's Python interface. It places DFFRAM RAM/RF designs in a predetermined structure to avoid a lengthy and inefficient manual placement process for RAM. Unlike `dffram.py`, `placeram` runs in a Docker container to avoid mandating an OpenROAD dependency (which is huge.) +`dffram.py` is an OpenLane-based flow that performs every step of hardening +the RAM modules from elaboration to GDS-II stream out. It incorporates `placeram`. # Dependencies -* A Unix-like Operating System - * Use the Windows Subsystem for Linux on Windows (which I use) -* The Skywater 130nm PDK - * See [Getting Sky130](./md/Getting%20Sky130.md) -* Docker Container -* Python 3.6+ with PIP - * PIP packages `click` and `pyyaml`: `python3 -m pip install click pyyaml` +* macOS or Linux +* The Nix Package Manager -## Recommended -* Klayout (to view the final result) +## Installing Nix +You can install Nix by following the instructions at https://nixos.org/download.html. + +Or more simply, on Ubuntu, run the following in your Terminal: + +```sh +sudo apt-get install -y curl +sh <(curl -L https://nixos.org/nix/install) --daemon --yes +``` +> On not systemd-based Linux systems, replace `--daemon` with `--no-daemon`. + +Or on macOS: + +```sh +sh <(curl -L https://nixos.org/nix/install) --yes +``` + +Enter your password if prompted. This hsould take around 5 minutes. + +Make sure to close all terminals after you're done with this step. + +### Setting up the binary cache +Cachix allows the reproducible Nix builds to be stored on a cloud server so you +do not have to build OpenLane's dependencies from scratch on every computer, +which will take a long time. + +First, you want to install Cachix by running the following in your terminal: + +```sh +nix-env -f "" -iA cachix +``` + +Then set up the OpenLane binary cache as follows: + +```sh +cachix use openlane +``` +If `cachix use openlane` fails, re-run it as follows: + +```sh +sudo env PATH="$PATH" cachix use openlane +``` # Basic ```sh -export PDK_ROOT=/usr/local/pdk git clone https://github.com/Cloud-V/DFFRAM cd DFFRAM -python3 -m pip install -r requirements.txt -./dffram.py -s 8x32 # <8-2048>x<8-64> +nix-shell +./dffram.py 8x32 # <8-2048>x<8-64> ``` # Advanced -The compilation flow at a minimum needs two options: the building blocks and the size. +The compilation flow has four main arguments: + * `--pdk`: The PDK + * `--scl`: The Standard Cell Library + * `--building-blocks`: The building blocks. + * The Size (passed without a flag) + +The building block full set `pdk:scl:blocks` corresponds to `./platforms///_building_blocks//model.v`. Building block sets are fundamentally similar with a number of exceptions, most importantly, the SCL used and supported sizes. + +For example: ```sh -export PDK_ROOT=/usr/local/pdk -./dffram.py -b sky130A:sky130_fd_sc_hd:ram -s 8x32 +./dffram.py -p sky130A -s sky130_fd_sc_hd -b ram 8x32 ``` -The building block set `pdk:scl:name` corresponds to `./platforms///_building_blocks//model.v`. Building block sets are fundamentally similar with a number of exceptions, most importantly, the SCL used and supported sizes. ## Options For a full list of options, please invoke: @@ -47,7 +91,6 @@ DFFRAM supports a number of secret options you can use to further customize your Variable Name|Effect -|- -PRFLOW_CREATE_IMAGE|If set to any value, a step after placement and routing that creates an image with Klayout is added. It's good for sanity checks. FORCE_ACCEPT_SIZE|DFFRAM checks that you are not using a size not officially marked supported as available by a certain building block set. If this environment variable is set to any value, the check is bypassed. FORCE_DESIGN_NAME|Design names are found based on the size. If you'd like to force dffram to use a specific design name instead, set this environment variable to that name. diff --git a/placeram/cli.py b/placeram/cli.py index 97e9267..7793af1 100644 --- a/placeram/cli.py +++ b/placeram/cli.py @@ -17,9 +17,12 @@ # limitations under the License. import os +import re +import traceback try: import odb + import utl except ImportError: print( """ @@ -44,10 +47,9 @@ from . import data from .row import Row + from .util import eprint from .reg_data import DFFRF -import re -import traceback class Placer: @@ -66,9 +68,12 @@ def __init__( odb.read_db(self.db, odb_in) # Technology Setup - self.lib = self.db.getLibs()[0] - self.sites = self.lib.getSites() - self.cells = self.lib.getMasters() + self.libs = self.db.getLibs() + self.sites = [] + self.cells = [] + for lib in self.libs: + self.sites += lib.getSites() + self.cells += lib.getMasters() ## Extract the fill cells for later use ### We use decap cells to substitute fills wherever possible. @@ -82,6 +87,7 @@ def __init__( filter(lambda x: re.match(fill_cell_data["tap"], x.getName()), self.cells) ) self.fill_cells_by_sites = {} + tap_width = None for cell in raw_fill_cells: match_info = re.match(fill_cell_data["fill"], cell.getName()) site_count = int(match_info[1]) @@ -98,6 +104,11 @@ def __init__( fill_cell_sizes = list(self.fill_cells_by_sites.keys()) + if tap_width is None: + eprint("No tap cells found!") + print(fill_cell_sizes) + exit(-1) + # Layout Setup self.block = self.db.getChip().getBlock() self.instances = self.block.getInsts() @@ -169,32 +180,23 @@ def place(self): % (self.core_width, self.core_height) ) + utl.metric_float("dffram__suggested__core_width", self.core_width) + utl.metric_float("dffram__suggested__core_height", self.core_height) + die_area = self.block.getDieArea().area() / ( self.micron_in_units * self.micron_in_units ) self.density = logical_area / die_area + utl.metric_float("dffram__logic__density", self.density) eprint("Density: %.2f%%" % (self.density * 100)) eprint("Done.") def write_db(self, output): return odb.write_db(self.db, output) == 1 - def write_width_height(self, dimensions_file): - try: - with open(dimensions_file, "w") as f: - f.write(str(self.core_width) + "x" + str(self.core_height)) - return True - except Exception: - return False - - def write_density(self, density_file): - try: - with open(density_file, "w") as f: - f.write(str(self.density)) - return True - except Exception: - return False + def write_def(self, output): + return odb.write_def(self.block, output) == 1 def check_readable(file): @@ -203,7 +205,8 @@ def check_readable(file): @click.command() -@click.option("-o", "--output", required=True) +@click.option("-o", "--output-odb", required=True) +@click.option("--output-def", type=str, required=False, default=None) @click.option("-s", "--size", required=True, help="RAM Size (ex. 8x32, 16x32…)") @click.option( "-r", @@ -211,31 +214,27 @@ def check_readable(file): required=False, help="File to print out text representation of hierarchy to. (Pass /dev/stderr or /dev/stdout for stderr or stdout.)", ) -@click.option( - "-d", - "--write-dimensions", - required=False, - help="File to print final width and height to (in the format '{width}x{height}')", -) -@click.option( - "-n", - "--write-density", - required=False, - help="File to print density to (in the format '{density}'- 0<=density<1)", -) @click.option( "-b", "--building-blocks", default="sky130A:sky130_fd_sc_hd:ram", help="Format :: : Name of the building blocks to use.", ) +@click.option( + "-l", + "--input-lef", + default=[], + help="Input LEF files (ignored)", + multiple=True, + type=str, +) @click.argument("odb_in", required=True, nargs=1) def cli( - output, + output_odb, + output_def, + input_lef, size, represent, - write_dimensions, - write_density, building_blocks, odb_in, ): @@ -290,23 +289,16 @@ def cli( placer.place() - if not placer.write_db(output): + if not placer.write_db(output_odb): eprint("Failed to write output ODB file.") exit(os.EX_IOERR) - eprint("Wrote to %s." % output) - - if write_dimensions is not None: - if not placer.write_width_height(write_dimensions): - eprint("Failed to write dimensions file.") - exit(os.EX_IOERR) - eprint("Wrote width and height to %s." % write_dimensions) + eprint("Wrote to %s." % output_odb) - if write_density is not None: - if not placer.write_density(write_density): - eprint("Failed to write density file.") + if output_def is not None: + if not placer.write_def(output_def): + eprint("Failed to write output DEF file.") exit(os.EX_IOERR) - eprint("Wrote density to %s." % write_dimensions) eprint("Done.") diff --git a/requirements.txt b/requirements.txt index 9358ae6..bae0622 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -click +openlane>=2.0.0a49 pyyaml -volare>=0.7.3 \ No newline at end of file +cloup \ No newline at end of file diff --git a/scripts/openlane/interactive.tcl b/scripts/openlane/interactive.tcl deleted file mode 100644 index 931a906..0000000 --- a/scripts/openlane/interactive.tcl +++ /dev/null @@ -1,54 +0,0 @@ - - -package require openlane -set script_dir [file dirname [file normalize [info script]]] -prep -design $script_dir -ignore_mismatches - -set ::env(LIB_SYNTH_COMPLETE_NO_PG) [list] -foreach lib $::env(LIB_SYNTH_COMPLETE) { - set fbasename [file rootname [file tail $lib]] - set lib_path [index_file $::env(synthesis_tmpfiles)/$fbasename.no_pg.lib] - convert_pg_pins $lib $lib_path - lappend ::env(LIB_SYNTH_COMPLETE_NO_PG) $lib_path -} - -set_odb $::env(INITIAL_ODB) -set_netlist $::env(INITIAL_NETLIST) - -set ::env(CURRENT_SDC) $::env(INITIAL_SDC) - -run_power_grid_generation - -global_routing -detailed_routing -run_antenna_check - -run_parasitics_sta -run_magic -if { [info exists ::env(ENABLE_KLAYOUT) ] } { - if { ($::env(ENABLE_KLAYOUT) == 1) } { - run_klayout - run_klayout_gds_xor - } -} - -run_magic_spice_export -run_lvs -run_magic_drc -if { [info exists ::env(ENABLE_KLAYOUT) ] } { - if { ($::env(ENABLE_KLAYOUT) == 1) } { - run_klayout_drc - } -} -if { [info exists ::env(ENABLE_CVC) ] } { - if { ($::env(ENABLE_CVC) == 1) } { - run_lef_cvc - } -} - -save_final_views -save_path $::env(PRODUCTS_PATH) - -calc_total_runtime -save_state -generate_final_summary_report -check_timing_violations diff --git a/scripts/perl/verilog_to_lib.pl b/scripts/perl/verilog_to_lib.pl deleted file mode 100755 index e020494..0000000 --- a/scripts/perl/verilog_to_lib.pl +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env perl -# Author: Sini Mukundan -# https://vlsi.pro/creating-lib-file-from-verilog/ - -use strict; - -if ($#ARGV < 1 ) { - print "usage: perl verilog_to_lib.pl [tran[cap[signal_level]]] \n"; - print " are all required"; - exit; -} - -my $module = $ARGV[0] ; -my $tran = 2.5 ; -my $cap = 0.001; -my $signal_level = "VDD" ; - -if(defined $ARGV[3]) {$tran = $ARGV[3];} -if(defined $ARGV[4]) {$cap = $ARGV[4];} -if(defined $ARGV[5]) {$signal_level = $ARGV[5];} - -my $FF; -my $FO; -open $FF, "< $ARGV[1]" or die "Can't open $ARGV[0] : $!"; -open $FO, ">$ARGV[2]" or die "Can't open $module.lib for write : $!"; - -my $db = createTopLevelDB(); -createDotLib($db,$FO); - -sub createDotLib -{ - my $topLevelDBRef = shift; - my $FO = shift ; - ### Header - print $FO "library\($topLevelDBRef->{'design'}->{'cell'}\) {\n"; - print $FO "\n /* unit attributes */\n"; - print $FO " time_unit : \"1ns\"\;\n"; - print $FO " voltage_unit : \"1V\"\;\n"; - print $FO " current_unit : \"1uA\"\;\n"; - print $FO " pulling_resistance_unit : \"1kohm\"\;\n"; - print $FO " leakage_power_unit : \"1nW\"\;\n"; - print $FO " capacitive_load_unit\(1,pf\)\;\n\n"; - foreach my $direction (keys(%{$topLevelDBRef->{'bus'}})) { - foreach my $bus_type (keys %{$topLevelDBRef->{'bus'}->{$direction}}) { - my @bus_width = split(/_/, $bus_type); - my $bus_hi = $bus_width[1] ; - my $bus_lo = $bus_width[2] ; - my $bus_width = $bus_hi+1-$bus_lo; - print $FO " type \($bus_type\) { \n"; - print $FO " base_type : array ; \n" ; - print $FO " data_type : bit \n" ;; - print $FO " bit_width : $bus_width \n" ;; - print $FO " bit_from : $bus_hi \n" ;; - print $FO " bit_to : $bus_lo ; \n" ; - print $FO " downto : true ; \n" ; - print $FO " } \n" ; - } - } - print $FO "\n cell\($topLevelDBRef->{'design'}->{'cell'}\) {\n"; - foreach my $direction (keys(%{$topLevelDBRef->{'pins'}})) { - foreach my $pin_name (@{$topLevelDBRef->{'pins'}->{$direction}}) { - print $FO (" pin\($pin_name\) { \n"); - print $FO ("\tdirection : $direction ;\n"); - if($direction eq "input") { - print $FO ("\tmax_transition : $tran;\n"); - } - print $FO ("\tcapacitance : $cap; \n"); - print $FO (" } \n") ; - } - } - foreach my $direction (keys(%{$topLevelDBRef->{'bus'}})) { - foreach my $bus_type (keys %{$topLevelDBRef->{'bus'}->{$direction}}) { - my @bus_width = split(/_/, $bus_type); - my $bus_hi = $bus_width[1] ; - my $bus_lo = $bus_width[2] ; - foreach my $bus_name (@{$topLevelDBRef->{'bus'}->{$direction}{$bus_type}}) { - chomp($bus_name); - print "BUS $bus_name : $bus_type : $direction \n" ; - print $FO (" bus\($bus_name\) { \n"); - print $FO ("\tbus_type : $bus_type ;\n"); - print $FO ("\tdirection : $direction ;\n"); - if($direction eq "input") { - print $FO ("\tmax_transition : $tran;\n"); - } - for(my $i=$bus_lo; $i<=$bus_hi; $i++) { - print $FO ("\tpin\($bus_name\[$i\]\) { \n"); - print $FO ("\t\tcapacitance : $cap; \n"); - print $FO ("\t} \n") ; - } - print $FO (" } \n") ; - } - } - } - print $FO (" } \n") ; - print $FO ("} \n") ; -} - -sub createTopLevelDB -{ - my $find_top_module = 0; - my %topLevelDB = () ; - my %pins = () ; - my %bus = () ; - my @input_pins ; - my @output_pins ; - my @inout_pins ; - my @bus_types ; - my %input_bus = () ; - my %output_bus = () ; - my %inout_bus = () ; - my %design = (); - $design{'cell'} = $module; - $design{'tran'} = $tran; - $design{'cap'} = $cap; - $design{'signal_level'} = $signal_level; - while(my $line = <$FF>) { - last if($find_top_module == 1); - if($line=~/module\s+$module/) { - $find_top_module = 1 ; - while(my $line = <$FF>) { - next if($line =~ "\s*//" ); - chomp($line); - if ($line =~/input\s+/ ) { - $line=~s/\s*input\s+//; - $line=~s/;//; - if($line =~/\[(\d+):(\d+)\]/) { - my $bus_type = "bus_$1_$2"; - $line=~s/\[(\d+):(\d+)\]//; - my @line = split(/,/, $line); - unless(grep {$_ eq $bus_type} @bus_types) { - push(@bus_types,$bus_type); - } - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@{$input_bus{$bus_type}}, $pin ); - } - } - else { - my @line = split(/,/, $line); - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@input_pins, $pin); - } - } - } - if ($line =~/output\s+/ ) { - $line=~s/\s*output\s+//; - $line=~s/;//; - if($line =~/\[(\d+):(\d+)\]/) { - my $bus_type = "bus_$1_$2"; - $line=~s/\[(\d+):(\d+)\]//; - my @line = split(/,/, $line); - unless(grep {$_ eq $bus_type} @bus_types) { - push(@bus_types,$bus_type); - } - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@{$output_bus{$bus_type}}, $pin ); - } - } - else { - my @line = split(/,/, $line); - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@output_pins, $pin); - } - } - - } - if ($line =~/inout\s+/ ) { - $line=~s/\s*inout\s+//; - $line=~s/;//; - if($line =~/\[(\d+):(\d+)\]/) { - my $bus_type = "bus_$1_$2"; - $line=~s/\[(\d+):(\d+)\]//; - my @line = split(/,/, $line); - unless(grep {$_ eq $bus_type} @bus_types) { - push(@bus_types,$bus_type); - } - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@{$inout_bus{$bus_type}}, $pin ); - } - } - else { - my @line = split(/,/, $line); - foreach my $pin (@line) { - $pin=~s/\s+//; - push(@inout_pins, $pin); - } - } - - } - - last if($line=~/endmodule/); - } - - } - } - $pins{'input'} = \@input_pins; - $pins{'output'} = \@output_pins; - $pins{'inout'} = \@inout_pins; - $bus{'input'} = \%input_bus; - $bus{'output'} = \%output_bus; - $bus{'inout'} = \%inout_bus; - $topLevelDB{'pins'} = \%pins; - $topLevelDB{'bus'} = \%bus; - $topLevelDB{'design'} = \%design; - return \%topLevelDB; -} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..ed8a01b --- /dev/null +++ b/shell.nix @@ -0,0 +1,23 @@ +# Copyright 2023 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/0218941ea68b4c625533bead7bbb94ccce52dceb.tar.gz") {} +}: + +let openlane-src = pkgs.fetchFromGitHub { + owner = "efabless"; + repo = "openlane2"; + rev = "95100bca1216ee83ff83ff5e425ad7c2fbece9f4"; + sha256 = "sha256-z9AZTjs2QwjL83+N4KztrjBNEE3zTxagPPtXPLZhRC4="; +}; in import "${openlane-src}/shell.nix" {} \ No newline at end of file