├── tests ├── __init__.py ├── specs │ ├── TestCadenceImport.yaml │ ├── TestViaCreation.yaml │ └── TestLRoute.yaml ├── test_cadence_import.py ├── via_test.py └── test_lroute.py ├── docs ├── .gitignore ├── requirements.txt ├── refresh_api.sh ├── source │ ├── api │ │ ├── modules.rst │ │ └── ACG.rst │ ├── common_use_cases │ │ ├── Autorouting.md │ │ ├── Transistor.md │ │ ├── Cadence_ACG_Mix.md │ │ ├── Floorplanning.md │ │ └── root.rst │ ├── quick_start │ │ ├── Installation.md │ │ ├── FirstLayout.md │ │ ├── root.rst │ │ └── CadenceInterface.md │ ├── getting_started │ │ ├── root.rst │ │ ├── CreatingHierarchicalCells.md │ │ ├── CreatingWires.md │ │ ├── DesignFlow.md │ │ └── AnatomyOfAGenerator.md │ ├── index.rst │ └── conf.py ├── environment.yml └── Makefile ├── examples ├── __init__.py ├── tech.yaml ├── specs │ ├── Getting_Started_Test2_specs.yaml │ └── example_specs.yaml ├── FillMeIn.py └── Getting_Started_Test2.py ├── ACG ├── __init__.py ├── tech.py ├── VirtualObj.py ├── Label.py ├── LayoutParse.py ├── XY.py ├── VirtualInst.py ├── PrimitiveUtil.py ├── Track.py ├── Via.py ├── AyarDesignManager.py ├── Rectangle.py ├── AyarLayoutGenerator.py ├── AutoRouterExtension.py └── AutoRouter.py ├── setup.py ├── readthedocs.yml ├── .gitignore ├── LICENSE └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/tech.yaml: -------------------------------------------------------------------------------- 1 | test_var1: '1' 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/ucb-art/BAG_framework@master#egg=bag -------------------------------------------------------------------------------- /docs/refresh_api.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sphinx-apidoc --force --output-dir=source/api ../ACG 3 | -------------------------------------------------------------------------------- /docs/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | ACG 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | ACG 8 | -------------------------------------------------------------------------------- /docs/source/common_use_cases/Autorouting.md: -------------------------------------------------------------------------------- 1 | # Autorouting 2 | 3 | TODO: Show all the different autorouter types available 4 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: docs_env 2 | dependencies: 3 | - shapely 4 | - rtree 5 | - recommonmark 6 | - pip: 7 | - "git+git://github.com/ucb-art/BAG_framework@master#egg=bag" 8 | -------------------------------------------------------------------------------- /ACG/__init__.py: -------------------------------------------------------------------------------- 1 | from .AyarDesignManager import AyarDesignManager 2 | from .AyarLayoutGenerator import AyarLayoutGenerator 3 | 4 | 5 | __version__ = '0.1.0' 6 | print(f'Loaded ACG v{__version__}') 7 | -------------------------------------------------------------------------------- /docs/source/common_use_cases/Transistor.md: -------------------------------------------------------------------------------- 1 | # Transistor Drawing and Row Generation 2 | 3 | 1. Show an example of a transistor drawing generator with the example technology 4 | 2. Show how to use transistor row generators 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | 4 | setup(name='ACG', 5 | version='0.1.0', 6 | description='ArbitraryCellGenerator', 7 | url='https://github.com/AyarLabs/ACG', 8 | packages=['ACG']) 9 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: latest 3 | 4 | python: 5 | version: 3.6 6 | use_system_site_packages: true 7 | 8 | conda: 9 | file: docs/environment.yml 10 | 11 | #requirements_file: docs/requirements.txt 12 | 13 | -------------------------------------------------------------------------------- /docs/source/quick_start/Installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | TODO: Step-by-step procedure to set up ACG 4 | 1. clone and pip install ACG 5 | 2. set up example technology files 6 | 3. run a quick wiring example 7 | 4. show how to incorporate with cadence 8 | -------------------------------------------------------------------------------- /tests/specs/TestCadenceImport.yaml: -------------------------------------------------------------------------------- 1 | impl_lib: 'Sandbox' 2 | impl_cell: 'ImportTest' 3 | 4 | layout_package: 'ACG.tests.test_cadence_import' 5 | layout_class: 'CadenceImportTest' 6 | 7 | layout_params: 8 | libname: 'Sandbox' 9 | cellname: 'TIALayout' 10 | -------------------------------------------------------------------------------- /docs/source/quick_start/FirstLayout.md: -------------------------------------------------------------------------------- 1 | # First Layout 2 | 3 | TODO: Step-by-step procedure to run a basic generator with a wire and a via 4 | 1. Show where the example script is 5 | 2. Describe how to run the script 6 | 3. Place a picture of what the output should look like 7 | -------------------------------------------------------------------------------- /docs/source/quick_start/root.rst: -------------------------------------------------------------------------------- 1 | Quick Start Guide 2 | ================== 3 | 4 | This chapter contains the fastest guide to getting your first generated layout. 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | Installation 10 | FirstLayout 11 | CadenceInterface 12 | -------------------------------------------------------------------------------- /docs/source/quick_start/CadenceInterface.md: -------------------------------------------------------------------------------- 1 | # Cadence Interface 2 | 3 | TODO: Step-by-step procedure to set up the Cadence interface, and generate your first OA file 4 | 1. Describe required lines to be added to the cdsinit files 5 | 2. Show how to change the generator to make an OA vs. a GDS 6 | -------------------------------------------------------------------------------- /ACG/tech.py: -------------------------------------------------------------------------------- 1 | """ 2 | When imported, this module will extract tech information for easy access from a module specified by the 'ACG_TECH' 3 | environment variable 4 | """ 5 | import yaml 6 | import os 7 | 8 | path = os.environ['ACG_TECH'] 9 | with open(path, 'r') as f: 10 | tech_info = yaml.load(f) 11 | -------------------------------------------------------------------------------- /docs/source/common_use_cases/Cadence_ACG_Mix.md: -------------------------------------------------------------------------------- 1 | # Incorporating Hard IP with ACG Generators 2 | 3 | TODO: Show how to make a buffer chain with custom designs 4 | 1. Describe how the cadence interface works 5 | 2. Show how to import a hand-generated cell as a master 6 | 3. Show an example of a cascading buffer chain 7 | -------------------------------------------------------------------------------- /docs/source/common_use_cases/Floorplanning.md: -------------------------------------------------------------------------------- 1 | # AMS Floorplanning and Assembly 2 | 3 | TODO: Show a floorplanning design methodology. 4 | 1. Provide an example of quick placeholder layout arrangement 5 | 2. Describe a flow where designers incrementally update cells and how to adapt with floorplanning 6 | 3. Show power stripe creation 7 | -------------------------------------------------------------------------------- /docs/source/common_use_cases/root.rst: -------------------------------------------------------------------------------- 1 | Common Use Cases 2 | ================== 3 | 4 | This chapter contains a set of examples that describe how to do several common design tasks. 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | :glob: 9 | 10 | Cadence_ACG_Mix 11 | Floorplanning 12 | Autorouting 13 | Transistor 14 | -------------------------------------------------------------------------------- /examples/specs/Getting_Started_Test2_specs.yaml: -------------------------------------------------------------------------------- 1 | impl_lib: 'ACG_examples' 2 | impl_cell: 'RoutingTest' 3 | 4 | layout_package: 'ACG.examples.Getting_Started_Test2' 5 | layout_class: 'RectTest' 6 | 7 | layout_params: 8 | num_connections: 5 9 | 10 | sweep_params: 11 | intent: [standard] 12 | 13 | root_dir: 'ACG/examples' 14 | -------------------------------------------------------------------------------- /docs/source/getting_started/root.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | ================== 3 | 4 | This chapter contains a more detailed guide to understanding how to use the core ACG feature set 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | DesignFlow 10 | AnatomyOfAGenerator 11 | CreatingWires 12 | CreatingHierarchicalCells 13 | -------------------------------------------------------------------------------- /docs/source/getting_started/CreatingHierarchicalCells.md: -------------------------------------------------------------------------------- 1 | # Creating Hierarchical Cells 2 | 3 | TODO: Explain how to create an inverter 4 | 1. Show how to run a generator within another generator 5 | 2. Explain the difference between masters and instances 6 | 3. Show how to place and align instances 7 | 4. Emphasize cacheing of masters 8 | 5. Example design of an inverter 9 | -------------------------------------------------------------------------------- /tests/specs/TestViaCreation.yaml: -------------------------------------------------------------------------------- 1 | impl_lib: 'Sandbox' 2 | impl_cell: 'ViaTest' 3 | 4 | layout_package: 'ACG.tests.via_test' 5 | layout_class: 'TestViaCreation' 6 | 7 | layout_params: 8 | bot_layer: 'M1' 9 | bot_x: .1 10 | bot_y: .1 11 | top_layer: 'M2' 12 | top_x: .1 13 | top_y: .1 14 | 15 | root_dir: '.' 16 | sweep_params: 17 | intent: [standard] 18 | -------------------------------------------------------------------------------- /docs/source/getting_started/CreatingWires.md: -------------------------------------------------------------------------------- 1 | # Creating Wires 2 | 3 | TODO: Explain how to create and move rectangles around 4 | 1. Explain philosophy of relative alignment and stretching 5 | 2. Provide examples of how to use the alignment and stretching functions, explain rectangle handles and edges. 6 | 3. Talk about tracks. 7 | 4. Briefly explain via creation methodology. 8 | 9 | -------------------------------------------------------------------------------- /tests/specs/TestLRoute.yaml: -------------------------------------------------------------------------------- 1 | impl_lib: 'Sandbox' 2 | impl_cell: 'LRouteTest' 3 | 4 | layout_package: 'ACG.tests.test_lroute' 5 | layout_class: 'TestLRoute' 6 | 7 | layout_params: 8 | bot_layer: 'M1' 9 | bot_size: [.1, 2] 10 | bot_loc: [2, 2] 11 | top_layer: 'M2' 12 | top_size: [2, .1] 13 | top_loc: [4, 4] 14 | 15 | root_dir: '.' 16 | sweep_params: 17 | intent: [standard] 18 | -------------------------------------------------------------------------------- /docs/source/getting_started/DesignFlow.md: -------------------------------------------------------------------------------- 1 | # Design Flow 2 | 3 | TODO: Explain high level architecture of ACG 4 | 1. All layout scripts are written as classes which subclass `AyarLayoutGenerator` 5 | 2. Your subclass of `AyarLayoutGenerator` defines how the layout should be drawn in terms of parameters that you define and technology constants 6 | 3. `AyarDesignManager` configures details about your technology, receives parameters from the user, and runs the generator to create an output layout. 7 | 4. Talk about spec files, and why they are useful 8 | -------------------------------------------------------------------------------- /docs/source/getting_started/AnatomyOfAGenerator.md: -------------------------------------------------------------------------------- 1 | # Anatomy of a Generator 2 | 3 | TODO: Explain detailed information about the ALG class 4 | 1. `AyarLayoutGenerator` provides helper methods to place and manipulate shapes and hierarchically place other instances and run other generators. Consider it to be your palette of functions you can use to describe your layout procedure. 5 | 2. `get_params_info()` should be defined to set which parameters your generator expects. 6 | 3. `AyarLayoutGenerator` defines an abstractmethod `draw_layout()` which contains the layout drawing algorithm in terms of your desired parameters. 7 | 8 | Show pictures of how a few of the key functions work 9 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = ACG 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /ACG/VirtualObj.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class VirtualObj(metaclass=abc.ABCMeta): 5 | """ 6 | Abstract class for creation of primitive objects 7 | """ 8 | def __init__(self): 9 | self.loc = {} 10 | 11 | def __getitem__(self, item): 12 | """ 13 | Allows for access of items inside the location dictionary without typing .loc[item] 14 | """ 15 | return self.export_locations()[str(item)] 16 | 17 | def export_locations(self): 18 | """ This method should return a dict of relevant locations for the virtual obj""" 19 | return self.loc 20 | 21 | @abc.abstractmethod 22 | def shift_origin(self, origin=(0, 0), orient='R0'): 23 | """ 24 | This method should shift the coordinates of relevant locations according to provided 25 | origin/transformation, and return a new shifted object. This is important to allow for deep manipulation in the 26 | hierarchy 27 | """ 28 | pass 29 | -------------------------------------------------------------------------------- /tests/test_cadence_import.py: -------------------------------------------------------------------------------- 1 | from bag import BagProject 2 | from ACG.AyarDesignManager import AyarDesignManager 3 | from ACG.AyarLayoutGenerator import AyarLayoutGenerator 4 | 5 | 6 | class CadenceImportTest(AyarLayoutGenerator): 7 | """ This tests the Cadence Layout import functionality """ 8 | 9 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 10 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 11 | 12 | @classmethod 13 | def get_params_info(cls): 14 | return dict( 15 | libname='library to import the cell from', 16 | cellname='name of cell to import' 17 | ) 18 | 19 | def layout_procedure(self): 20 | master = self.import_cadence_layout(libname=self.params['libname'], 21 | cellname=self.params['cellname']) 22 | inst = self.add_instance(master=master, loc=(10, 10)) 23 | print(inst.loc) 24 | 25 | 26 | if __name__ == '__main__': 27 | local_dict = locals() 28 | if 'bprj' not in local_dict: 29 | print('creating BAG project') 30 | bprj = BagProject() 31 | else: 32 | print('loading BAG project') 33 | bprj = local_dict['bprj'] 34 | 35 | spec_file = 'ACG/tests/specs/TestCadenceImport.yaml' 36 | ADM = AyarDesignManager(bprj, spec_file) 37 | ADM.generate_layout() 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Laygo related files # 2 | ########################### 3 | .idea 4 | *.pyc 5 | *.layermap 6 | #*.gds 7 | #*.yaml 8 | *~ 9 | *.attr 10 | 11 | # Cadence generated files # 12 | ########################### 13 | *.auCdl 14 | LIB_c4* 15 | *cdslck* 16 | DefaultVlogALib 17 | modelsim.ini 18 | *.log* 19 | cdssetup 20 | menus 21 | *.hcell 22 | .ukcds5 23 | .cadence 24 | pls* 25 | *.oacache 26 | *.oa- 27 | .oalib 28 | *.abstract.options.prev 29 | *.abstract.options 30 | *.abstract.status 31 | *.abstract.messages 32 | *thumbnail_*.png 33 | *.swp 34 | *abstract#2* 35 | .skillide* 36 | map 37 | netlist 38 | svdb 39 | si.env 40 | 41 | # Packages # 42 | ############ 43 | *.7z 44 | *.dmg 45 | *.gz 46 | *.iso 47 | *.jar 48 | *.rar 49 | *.tar 50 | *.zip 51 | 52 | # OS generated files # 53 | ####################### 54 | .nfs* 55 | .DS_Store 56 | .DS_Store? 57 | ._* 58 | .SpotLight-V100 59 | .Trashes 60 | ehthumbs.db 61 | *.*~ 62 | *~* 63 | precursor2*dat 64 | *cdslck* 65 | DefaultVlogALib 66 | modelsim.ini 67 | .submodule_is_installed 68 | *.log* 69 | cdssetup 70 | menus 71 | *.hcell 72 | .ukcds5 73 | .cadence 74 | pls* 75 | *.oacache 76 | *.oa- 77 | .oalib 78 | *.abstract.options.prev 79 | *.abstract.options 80 | *.abstract.status 81 | *.abstract.messages 82 | *thumbnail_*.png 83 | *.swp 84 | *tmp.* 85 | *abstract#2* 86 | abstract.* 87 | .abstract 88 | lefout.list 89 | *.gds 90 | *.calibre.db 91 | *.lef 92 | *_sandbox 93 | *.err.msg 94 | 95 | #Soft Link# 96 | ########## 97 | PCELLSKILL 98 | 99 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. ACG documentation master file, created by 2 | sphinx-quickstart on Wed Jun 6 14:22:25 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to ACG's documentation! 7 | =============================== 8 | Ayar Custom Generator(ACG) is a package that enables full custom layout creation without a grid. It allows a designer to describe layout generation script in the Python language and automate the layout process 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Contents: 13 | 14 | quick_start/root 15 | getting_started/root 16 | common_use_cases/root 17 | api/modules 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | Introduction 27 | ============ 28 | Documentation for available functions and classes can be found at :ref:`modindex` 29 | 30 | Arbitrary Cell Generator is a plugin to Berkeley Analog Generator 2.0 (BAG) that enables parametrized 31 | grid-free layout creation. The main goal of ACG is to make it easy to create highly customized layout generators that 32 | may not be possible in BAG due to its requirement to make layouts technology agnostic. In addition, we make it easy 33 | to combine legacy hand-drawn layouts and IP together with new layout generators. 34 | 35 | NOTE: ACG is currently in development, and is being slowly cleaned up for open-source consumption, use at your own risk! 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, AyarLabs 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACG 2 | 3 | [![Documentation Status](https://readthedocs.org/projects/acg/badge/?version=latest)](https://acg.readthedocs.io/en/latest/?badge=latest) 4 | 5 | Arbitrary Cell Generator is a plugin to Berkeley Analog Generator 2.0 (BAG) which is described in detail in the following papers: 6 | [BAG2: A Process-Portable Framework for Generator-Based AMS Circuit Design](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8357061) 7 | and [Closing the Analog Design Loop with Berkeley Analog Generator](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2019/EECS-2019-23.pdf). 8 | Documentation for ACG can be found at . 9 | The main goal of ACG is to make it easy to create highly customized layout generators that may not be possible in BAG due to its 10 | requirement to make layouts technology agnostic. In addition, we make it easy to combine legacy hand-drawn 11 | layouts and IP together with new layout generators. 12 | 13 | NOTE: ACG is currently in development, and is being slowly cleaned up for open-source consumption, use at your own risk! 14 | 15 | 16 | ## Unique Features 17 | In short, BAG encourages a generator-based analog integrated circuit (IC) design methodology. 18 | This methodology enables the entire IC design procedure, from schematic design and simulation, to layout and optimization to be encapsulated in a set of Python scripts that are self-documenting, reproducible, reusable, and portable to different technologies and tapeouts. 19 | 20 | ACG specifically aims to enhance the layout generation portion of the BAG flow. 21 | Currently, BAG layout emphasizes the use of an abstraction layer called `AnalogBase`. 22 | This class encapsulates the procedure of drawing transistor rows and metal routing in a way that is completely agnostic of the technology node you are using. 23 | This makes porting design scripts from one technology to another extremely fast, but comes at the cost of losing some control over detailed layout optimizations. 24 | In addition, incorporating `AnalogBase` cells together with hand-made cells and other external IP is not straightforward. 25 | 26 | -------------------------------------------------------------------------------- /tests/via_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | via_test.py 3 | 4 | Unit test module for ACG that exercises the creation of metals and vias. 5 | """ 6 | from ACG.AyarLayoutGenerator import AyarLayoutGenerator 7 | 8 | 9 | class TestViaCreation(AyarLayoutGenerator): 10 | """ 11 | Class that creates PC and M1 shapes and attempts to place vias 12 | """ 13 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 14 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 15 | 16 | @classmethod 17 | def get_params_info(cls): 18 | return dict( 19 | bot_layer='name of the bottom layer', 20 | top_layer='name of the top layer', 21 | bot_x='x size in microns of the bot layer', 22 | bot_y='y size in microns of the bot layer', 23 | top_x='x size in microns of the top layer', 24 | top_y='y size in microns of the top layer' 25 | ) 26 | 27 | def layout_procedure(self): 28 | print('--- Running via creation test ---') 29 | bot_layer = self.params['bot_layer'] 30 | top_layer = self.params['top_layer'] 31 | bot_x = self.params['bot_x'] 32 | bot_y = self.params['bot_y'] 33 | top_x = self.params['top_x'] 34 | top_y = self.params['top_y'] 35 | 36 | # Create a default rectangle for each layer 37 | bot = self.add_rect(layer=bot_layer) 38 | top = self.add_rect(layer=top_layer) 39 | 40 | # Set the size of the PC and M1 rectangles to be the minimum 41 | bot.set_dim('x', bot_x) 42 | bot.set_dim('y', bot_y) 43 | top.set_dim('x', top_x) 44 | top.set_dim('y', top_y) 45 | 46 | # Attempt to place a via in between them 47 | via = self.connect_wires(bot, top) 48 | # via = self.connect_wires(bot, top, extend=(False, False)) 49 | 50 | 51 | if __name__ == '__main__': 52 | """ Via Unit Test """ 53 | from bag import BagProject 54 | from ACG.AyarDesignManager import AyarDesignManager 55 | 56 | # The following code checks if there is a bag project already running and uses it 57 | local_dict = locals() 58 | if 'bprj' not in local_dict: 59 | print('creating BAG project') 60 | bprj = BagProject() 61 | else: 62 | print('loading BAG project') 63 | bprj = local_dict['bprj'] 64 | 65 | spec_file = 'ACG/tests/specs/TestViaCreation.yaml' 66 | ADM = AyarDesignManager(bprj, spec_file) 67 | ADM.generate_layout() 68 | -------------------------------------------------------------------------------- /tests/test_lroute.py: -------------------------------------------------------------------------------- 1 | from ACG.AyarLayoutGenerator import AyarLayoutGenerator 2 | from ACG.AutoRouter import AutoRouter 3 | 4 | 5 | class TestLRoute(AyarLayoutGenerator): 6 | """ 7 | Class that creates PC and M1 shapes and attempts to place vias 8 | """ 9 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 10 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 11 | 12 | @classmethod 13 | def get_params_info(cls): 14 | return dict( 15 | bot_layer='name of the bottom layer', 16 | top_layer='name of the top layer', 17 | bot_loc='center in microns of the bot layer', 18 | bot_size='xy size in microns of the bot layer', 19 | top_loc='center in microns of the top layer', 20 | top_size='xy size in microns of the top layer' 21 | ) 22 | 23 | def layout_procedure(self): 24 | print('--- Running LRoute test ---') 25 | bot_layer = self.params['bot_layer'] 26 | top_layer = self.params['top_layer'] 27 | bot_loc = self.params['bot_loc'] 28 | bot_size = self.params['bot_size'] 29 | top_loc = self.params['top_loc'] 30 | top_size = self.params['top_size'] 31 | 32 | # Create a default rectangle for each layer 33 | bot = self.add_rect(layer=bot_layer) 34 | top = self.add_rect(layer=top_layer) 35 | 36 | # Size up and align the rectangles 37 | bot.set_dim('x', bot_size[0]) 38 | bot.set_dim('y', bot_size[1]) 39 | bot.align(target_handle='c', offset=bot_loc) 40 | top.set_dim('x', top_size[0]) 41 | top.set_dim('y', top_size[1]) 42 | top.align(target_handle='c', offset=top_loc) 43 | 44 | # Attempt to place a route in between them 45 | router = AutoRouter(self) 46 | router.stretch_l_route(start_rect=bot, start_dir='y', end_rect=top) 47 | 48 | 49 | if __name__ == '__main__': 50 | """ Via Unit Test """ 51 | from bag import BagProject 52 | from ACG.AyarDesignManager import AyarDesignManager 53 | 54 | # The following code checks if there is a bag project already running and uses it 55 | local_dict = locals() 56 | if 'bprj' not in local_dict: 57 | print('creating BAG project') 58 | bprj = BagProject() 59 | else: 60 | print('loading BAG project') 61 | bprj = local_dict['bprj'] 62 | 63 | spec_file = 'ACG/tests/specs/TestLRoute.yaml' 64 | ADM = AyarDesignManager(bprj, spec_file) 65 | ADM.generate_layout() 66 | -------------------------------------------------------------------------------- /examples/FillMeIn.py: -------------------------------------------------------------------------------- 1 | from bag import BagProject 2 | from ACG.AyarDesignManager import AyarDesignManager 3 | from ACG.AyarLayoutGenerator import AyarLayoutGenerator 4 | 5 | 6 | class FillMeIn(AyarLayoutGenerator): 7 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 8 | # Call ALG's constructor 9 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 10 | 11 | # dict for storing important locations 12 | self.loc = { 13 | 'example1': [], 14 | 'example2': [] 15 | } 16 | 17 | @classmethod 18 | def get_params_info(cls): 19 | """ Return a dictionary of parameter descriptions """ 20 | return dict( 21 | ex1='text description of param', 22 | ex2='text description of param', 23 | ex3='text description of param' 24 | ) 25 | 26 | @classmethod 27 | def get_default_param_values(cls): 28 | """ Return a dictionary describing default parameter values """ 29 | return dict( 30 | ex3=.07 # any subset of the parameters can be provided with defaults 31 | ) 32 | 33 | def export_locations(self): 34 | """ 35 | Returns a dictionary of shapes/inst in the layout. If you would like to use a different variable than 36 | self.loc, override export_locations() here. 37 | """ 38 | return self.loc 39 | 40 | def layout_procedure(self): 41 | """ Implement this method to describe how the layout is drawn """ 42 | # It is recommended to break up the procedure into multiple functions that can be called 43 | # independently. This makes it easy to subclass and override in future implementations 44 | # Example procedure: 45 | self.create_master() 46 | self.place_instance() 47 | self.connect_nets() 48 | self.draw_pins() 49 | 50 | def create_master(self): 51 | pass 52 | 53 | def place_instance(self): 54 | pass 55 | 56 | def connect_nets(self): 57 | pass 58 | 59 | def draw_pins(self): 60 | pass 61 | 62 | 63 | if __name__ == '__main__': 64 | # The following code checks if there is a bag project already running and uses it 65 | local_dict = locals() 66 | if 'bprj' not in local_dict: 67 | print('creating BAG project') 68 | bprj = BagProject() 69 | else: 70 | print('loading BAG project') 71 | bprj = local_dict['bprj'] 72 | 73 | spec_file = 'Path/to/spec/file.yaml' 74 | ALM = AyarDesignManager(bprj, spec_file=spec_file) 75 | ALM.generate_layout() 76 | -------------------------------------------------------------------------------- /docs/source/api/ACG.rst: -------------------------------------------------------------------------------- 1 | ACG package 2 | =========== 3 | 4 | Submodules 5 | ---------- 6 | 7 | ACG.AutoRouter module 8 | --------------------- 9 | 10 | .. automodule:: ACG.AutoRouter 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | ACG.AutoRouterExtension module 16 | ------------------------------ 17 | 18 | .. automodule:: ACG.AutoRouterExtension 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | ACG.AyarDesignManager module 24 | ---------------------------- 25 | 26 | .. automodule:: ACG.AyarDesignManager 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | ACG.AyarLayoutGenerator module 32 | ------------------------------ 33 | 34 | .. automodule:: ACG.AyarLayoutGenerator 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | ACG.Label module 40 | ---------------- 41 | 42 | .. automodule:: ACG.Label 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | ACG.LayoutParse module 48 | ---------------------- 49 | 50 | .. automodule:: ACG.LayoutParse 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | ACG.PrimitiveUtil module 56 | ------------------------ 57 | 58 | .. automodule:: ACG.PrimitiveUtil 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | ACG.Rectangle module 64 | -------------------- 65 | 66 | .. automodule:: ACG.Rectangle 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | 71 | ACG.Track module 72 | ---------------- 73 | 74 | .. automodule:: ACG.Track 75 | :members: 76 | :undoc-members: 77 | :show-inheritance: 78 | 79 | ACG.Via module 80 | -------------- 81 | 82 | .. automodule:: ACG.Via 83 | :members: 84 | :undoc-members: 85 | :show-inheritance: 86 | 87 | ACG.VirtualInst module 88 | ---------------------- 89 | 90 | .. automodule:: ACG.VirtualInst 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | 95 | ACG.VirtualObj module 96 | --------------------- 97 | 98 | .. automodule:: ACG.VirtualObj 99 | :members: 100 | :undoc-members: 101 | :show-inheritance: 102 | 103 | ACG.XY module 104 | ------------- 105 | 106 | .. automodule:: ACG.XY 107 | :members: 108 | :undoc-members: 109 | :show-inheritance: 110 | 111 | ACG.tech module 112 | --------------- 113 | 114 | .. automodule:: ACG.tech 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | 119 | 120 | Module contents 121 | --------------- 122 | 123 | .. automodule:: ACG 124 | :members: 125 | :undoc-members: 126 | :show-inheritance: 127 | -------------------------------------------------------------------------------- /examples/specs/example_specs.yaml: -------------------------------------------------------------------------------- 1 | # example_specs.yaml 2 | # This file contains the basic variables required to specify a design in ACG 3 | 4 | # This file is parsed into nested dicts by the AyarDesignManager class; its data can be accessed by any subclass through 5 | # reading self.specs 6 | 7 | impl_lib: 'example_lib' # Library name in virtuoso where the generated designs will be stored 8 | impl_cell: 'example_cell' # Cell name in virtuoso where the generated design will be stored 9 | 10 | sch_temp_lib: 'sch_temp_lib' # Library name in virtuoso where the schematic template is currently stored 11 | sch_temp_cell: 'sch_temp_cell' # Cell name of the schematic template 12 | 13 | layout_package: 'ACG_Core_Layout.DiffAmp.RLoad_Diff_Amp' # Name of the package that contains your layout generator class 14 | layout_class: 'RLoadDiffAmpLayout' # Name of the top level python class in layout_package that generates your layout 15 | 16 | # These parameters are required to generate a testbench and run simulations. 17 | tb_params: 18 | # Each of the following specifies 1 testbench 19 | example_tb1: 20 | tb_lib: 'tb_lib' # Library in virtuoso where the testbench template can be found 21 | tb_cell: 'tb1_cell' # Cell in tb_lib where the testbench template can be found 22 | tb_sch_params: 23 | # These are used to specify the testbench schematic generator 24 | param1: {} 25 | param2: {} 26 | tb_sim_params: 27 | # These are used as parameters in the ADEXL simulation state 28 | param1: 0 29 | param2: 100.0e+9 30 | data_dir: 'example_tb1_data' # directory name in chip_dev where the results of this sim are stored 31 | sim_envs: ['tt'] # list of corners to run this test on 32 | view_name: 'schematic' # view to set the config file to use for simulation 33 | example_tb2: 34 | tb_lib: 'tb_lib' 35 | tb_cell: 'tb2_cell' 36 | tb_sch_params: 37 | param1: {} 38 | param2: {} 39 | tb_sim_params: 40 | param1: 0 41 | param2: 100.0e+9 42 | data_dir: 'example_tb2_data' 43 | sim_envs: ['ff_hot'] 44 | view_name: 'netlist' # netlist views are generated by PEX, and can be used in the simulation 45 | 46 | root_dir: 'data' # Root directory where the data is stored 47 | sweep_params: # Defines which parameters are to be swept in simulation 48 | intent: [standard] # This must exist even if you don't run simulations, Eric will change this soon 49 | 50 | # These parameter dicts are dependent on how your design script operates. 51 | # Ex: If your design class calculates the layout and schematic parameters, you do not need layout/sch sections 52 | dsn_params: 53 | layout_params: 54 | sch_params: 55 | -------------------------------------------------------------------------------- /ACG/Label.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import numpy as np 3 | # ACG imports 4 | from ACG.VirtualObj import VirtualObj 5 | from ACG.PrimitiveUtil import Mt 6 | from ACG.XY import XY 7 | from typing import TYPE_CHECKING 8 | if TYPE_CHECKING: 9 | from ACG.Rectangle import Rectangle 10 | 11 | 12 | class Label(VirtualObj): 13 | """ 14 | Primitive class to describe a label on xy plane and various associated utility functions 15 | Keeps all coordinates on the grid 16 | """ 17 | 18 | def __init__(self, 19 | name, 20 | layer, 21 | xy, 22 | res=.001 # type: float 23 | ): 24 | 25 | VirtualObj.__init__(self) 26 | # Set the resolution of the grid 27 | self._res = res 28 | self._xy = XY(xy) 29 | self._name = name 30 | self._layer = layer 31 | 32 | @property 33 | def xy(self): 34 | return self._xy 35 | 36 | @property 37 | def x(self): 38 | return self._xy.x 39 | 40 | @property 41 | def y(self): 42 | return self._xy.y 43 | 44 | @property 45 | def name(self): 46 | return self._name 47 | 48 | @property 49 | def layer(self): 50 | return self._layer 51 | 52 | """ Utility Functions """ 53 | 54 | def contained_by(self, rect: "Rectangle") -> bool: 55 | """ 56 | Determines whether or not this label is contained by the provided rectangle. This is useful 57 | when trying to associate labels with drawn metals 58 | 59 | Parameters 60 | ---------- 61 | rect : Rectangle 62 | Rectangle to check for enclosure 63 | 64 | Returns 65 | ------- 66 | contained : bool 67 | True if the label and rectangle overlap 68 | """ 69 | # Check that we are in between the left and right edges 70 | if self.x >= rect.loc['l'] and self.x <= rect.loc['r']: 71 | # Then check that we are in between the top and bottom edges 72 | if self.y >= rect.loc['b'] and self.y <= rect.loc['t']: 73 | return True 74 | else: 75 | return False 76 | 77 | def export_locations(self): 78 | """ For now just returns a dict of the coordinates """ 79 | return { 80 | 'x': [self.x], 81 | 'y': [self.y] 82 | } 83 | 84 | def shift_origin(self, origin=(0, 0), orient='R0'): 85 | transform = Mt(orient) # Convert the rotation string to a matrix transformation 86 | # Apply the transformation to the coordinate 87 | new_xy = np.transpose(np.matmul(transform, np.transpose(np.asarray(self.xy)))) 88 | # Convert to XY coordinates and return shifted coordinate 89 | return XY(new_xy) + XY(origin) 90 | -------------------------------------------------------------------------------- /ACG/LayoutParse.py: -------------------------------------------------------------------------------- 1 | from ACG.Rectangle import Rectangle 2 | from ACG.Label import Label 3 | from typing import Dict 4 | 5 | 6 | class CadenceLayoutParser: 7 | """ 8 | This class acts as a database that will contain and manipulate layout shapes. Initially the plan 9 | is to only support Rectangles, Vias, and Instances. Support for other shapes will come as needed 10 | """ 11 | 12 | def __init__(self, raw_content: dict): 13 | # Initialize dict of lists that will contain the shapes that we generate. These are 14 | # indexed by their layer 15 | self._rect_list: Dict[str, list] = {} 16 | self._via_list: Dict[str, list] = {} 17 | self._inst_list: Dict[str, list] = {} 18 | self._label_list: Dict[str, list] = {} 19 | 20 | # Store the raw data for Parsing 21 | self._raw_content = raw_content 22 | self._parse_rects() 23 | self._parse_labels() 24 | 25 | def generate_loc_dict(self) -> dict: 26 | """ 27 | Takes all of the parsed content and generates a location dictionary that can be used by 28 | an ACG instance. This method will automatically associate labels with any metal 29 | rectangles that they overlap with and place them in the location dictionary accessible by 30 | the name on the label. If multiple labels of the same net exist, a list of associated 31 | rectangles is created instead. It also automatically creates a boundary based on the 32 | prBoundary embedded in the design 33 | 34 | Returns 35 | ------- 36 | loc_dict : dict 37 | location dictionary that can be used directly by an ACG instance 38 | """ 39 | loc_dict = {} 40 | # First generate a boundary from the drawn prBoundary 41 | if 'prBoundary' in self._rect_list: 42 | loc_dict['bnd'] = self._rect_list['prBoundary'][0] 43 | else: 44 | print(f"WARNING: {self._raw_content['cell_name']} does not contain a prBoundary") 45 | 46 | # Check if the labels overlap with any rectangles on the same layer 47 | # TODO: This code is dirty... there must be a better way 48 | for layer, label_list in self._label_list.items(): 49 | for label in label_list: 50 | for rect in self._rect_list.get(layer, []): 51 | if label.contained_by(rect): 52 | if label.name not in loc_dict: 53 | loc_dict[label.name] = rect 54 | else: 55 | if isinstance(loc_dict[label.name], list): 56 | loc_dict[label.name].append(rect) 57 | else: 58 | loc_dict[label.name] = [loc_dict[label.name]] 59 | loc_dict[label.name].append(rect) 60 | return loc_dict 61 | 62 | def _parse_labels(self) -> None: 63 | """ 64 | Tales all of the labels from the raw content and generates new virtual labels from them 65 | """ 66 | for _, label in self._raw_content['labels'].items(): 67 | # Extract the information from the raw content 68 | layer = self._parse_layer(label['layer']) 69 | xy = label['xy'] 70 | # Generate a new virtual label to mirror this cadence label 71 | new_label = Label(name=label['label'], 72 | layer=layer, 73 | xy=xy) 74 | if layer[0] not in self._label_list: 75 | self._label_list[layer[0]] = [new_label] 76 | else: 77 | self._label_list[layer[0]].append(new_label) 78 | 79 | def _parse_rects(self) -> None: 80 | """ 81 | Takes all of the rectangles from the raw content and generates new virtual rectangles 82 | from them. 83 | """ 84 | for _, rect in self._raw_content['rects'].items(): 85 | # Extract the information from the raw content 86 | layer = self._parse_layer(rect['layer']) 87 | xy = rect['bBox'] 88 | # Generate a new virtual rect to mirror this cadence rectangle 89 | new_rect = Rectangle(layer=layer, xy=xy, virtual=True) 90 | if layer[0] not in self._rect_list: 91 | self._rect_list[layer[0]] = [new_rect] 92 | else: 93 | self._rect_list[layer[0]].append(new_rect) 94 | 95 | def _parse_layer(self, layer_str: str): 96 | """ 97 | The SKILL interface returns the layer in a single string containing both the layer and 98 | purpose. This method takes in that string and returns the appropriate tuple layer type 99 | 100 | Parameters 101 | ---------- 102 | layer_str : str 103 | Raw layer string provided by SKILL interface 104 | 105 | Returns 106 | ------- 107 | layer : tuple[str, str] 108 | Layer purpose pair 109 | """ 110 | return tuple(layer_str.split()) 111 | -------------------------------------------------------------------------------- /examples/Getting_Started_Test2.py: -------------------------------------------------------------------------------- 1 | from bag import BagProject 2 | from ACG.AyarDesignManager import AyarDesignManager 3 | from ACG.AyarLayoutGenerator import AyarLayoutGenerator 4 | 5 | 6 | class RectTest(AyarLayoutGenerator): 7 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 8 | # Call ALG's constructor 9 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 10 | 11 | # dict for storing important locations 12 | self.loc = { 13 | 'output': [], 14 | 'input': [] 15 | } 16 | 17 | @classmethod 18 | def get_params_info(cls): 19 | """ Return a dictionary of parameter descriptions """ 20 | return dict( 21 | num_connections='number of input-output connections to be made', 22 | input_width='width of the input lines', 23 | input_metal='metal layer of the input lines', 24 | input_spacing='space between input lines', 25 | input_start_x='x coordinate location of the first input line', 26 | output_width='width of the output lines', 27 | output_metal='metal layer of the output lines', 28 | output_spacing='space between output lines', 29 | output_start_y='y coordinate location of the first output line' 30 | ) 31 | 32 | @classmethod 33 | def get_default_param_values(cls): 34 | """ Return a dictionary describing default parameter values """ 35 | return dict( 36 | input_width=.1, 37 | input_metal='M1', 38 | input_spacing=.2, 39 | input_start_x=.7, 40 | output_width=.15, 41 | output_metal='M3', 42 | output_spacing=.15, 43 | output_start_y=1 44 | ) 45 | 46 | def layout_procedure(self): 47 | self.setup_output() 48 | self.setup_input() 49 | self.draw_connections() 50 | 51 | def setup_output(self): 52 | """ Prepares the output pin locations """ 53 | for count in range(int(self.params['num_connections'])): 54 | # Adds a rect of given metal layer, defaults to 100n x 100n size placed at the origin 55 | output_rect = self.add_rect(layer=self.params['output_metal']) # self.add_rect returns a rect object 56 | output_rect.set_dim(dim='y', size=self.params['output_width']) 57 | if count >= 1: 58 | # Aligns lower left corner of the rectangle to the upper left corner of the previous rectangle 59 | # with given output spacing 60 | output_rect.align('ll', ref_rect=self.loc['output'][-1], ref_handle='ul', 61 | offset=(0, self.params['output_spacing'])) 62 | else: 63 | # Aligns lower left corner of the first rectangle to (0, 1) 64 | output_rect.align('ll', offset=(0, self.params['output_start_y'])) 65 | self.loc['output'].append(output_rect) 66 | 67 | def setup_input(self): 68 | """ Prepares the input pin locations """ 69 | # Creates rect of provided metal layer and default size to origin 70 | input_rect = self.add_rect(self.params['input_metal']) 71 | input_rect.set_dim(dim='x', size=self.params['input_width']) 72 | # Moves first input rectangle to align lower left corner with the start position 73 | input_rect.align('ll', offset=(self.params['input_start_x'], 0)) 74 | self.loc['input'].append(input_rect) 75 | # Create consistently spaced reference lines for where the input wires should go 76 | x_track = self.add_track(dim='x', spacing=self.params['input_spacing'] + self.params['input_width']) 77 | x_track.align(ref_rect=input_rect, ref_handle='ll') # Align track 0 to the lower left corner of the input 78 | for count in range(1, int(self.params['num_connections'])): 79 | input_temp = self.copy_rect(input_rect) # Creates a new rectangle with same size and layer as input_rect 80 | input_temp.align('ll', track=x_track(count)) # Aligns it to appropriate track location 81 | self.loc['input'].append(input_temp) 82 | 83 | def draw_connections(self): 84 | """ Connects the input and output wires """ 85 | # Stretch each output wire to the corresponding input wire 86 | # zip creates a list of input/output rect tuples to loop over 87 | for in_rect, out_rect in zip(self.loc['input'], self.loc['output']): 88 | # stretch_opt allows you to disable stretching in a given direction 89 | out_rect.stretch('ur', ref_rect=in_rect, ref_handle='ur', stretch_opt=(True, False)) 90 | in_rect.stretch('t', ref_rect=out_rect, ref_handle='t') 91 | self.connect_wires(in_rect, out_rect) 92 | 93 | 94 | if __name__ == '__main__': 95 | # The following code checks if there is a bag project already running and uses it 96 | local_dict = locals() 97 | if 'bprj' not in local_dict: 98 | print('creating BAG project') 99 | bprj = BagProject() 100 | else: 101 | print('loading BAG project') 102 | bprj = local_dict['bprj'] 103 | 104 | spec_file = 'ACG/examples/specs/Getting_Started_Test2_specs.yaml' 105 | 106 | ALM = AyarDesignManager(bprj, spec_file=spec_file) 107 | ALM.generate_layout() 108 | -------------------------------------------------------------------------------- /ACG/XY.py: -------------------------------------------------------------------------------- 1 | import numbers 2 | import numpy as np 3 | # ACG imports 4 | from ACG.VirtualObj import VirtualObj 5 | from ACG.PrimitiveUtil import Mt 6 | 7 | 8 | class XY(VirtualObj): 9 | """ 10 | Primitive class to describe a single coordinate on xy plane and various associated utility functions 11 | Keeps all coordinates on the grid 12 | """ 13 | 14 | def __init__(self, 15 | xy, 16 | res=.001 # type: float 17 | ): 18 | 19 | VirtualObj.__init__(self) 20 | # Set the resolution of the grid 21 | self._res = res 22 | # Create the internal x and y variable names 23 | self._x = None 24 | self._y = None 25 | 26 | # Perform input conditioning before storing data 27 | if isinstance(xy, XY): 28 | # Immediately copy the coordinates 29 | self.xy = xy 30 | elif len(xy) != 2: 31 | # If the provided value does not have 1 number for x and 1 for y 32 | raise ValueError('{}:{} does not have length 2'.format(type(xy), xy)) 33 | elif all([isinstance(xy[0], numbers.Real), isinstance(xy[1], numbers.Real)]): 34 | # If the provided values contain real numbers, store them in xy 35 | self.xy = xy 36 | else: 37 | raise TypeError('{} type does not represent a valid xy coordinate description'.format(type(xy))) 38 | 39 | """ Magic methods """ 40 | 41 | def __repr__(self): 42 | return 'XY([{}, {}])'.format(self.x, self.y) 43 | 44 | def __str__(self): 45 | return '[{}, {}]'.format(self.x, self.y) 46 | 47 | def __len__(self): 48 | return 2 49 | 50 | def __getitem__(self, item): 51 | """ Treat the xy coordinate as either an indexed array or dictionary when getting values""" 52 | if item == 0: 53 | return self.x 54 | elif item == 1: 55 | return self.y 56 | elif item == 'x': 57 | return self.x 58 | elif item == 'y': 59 | return self.y 60 | else: 61 | raise ValueError('{} is an invalid coordinate index'.format(item)) 62 | 63 | def __setitem__(self, key, value): 64 | """ Treat the xy coordinate as either an indexed array or dictionary when setting values""" 65 | if key == 0: 66 | self.x = value 67 | elif key == 1: 68 | self.y = value 69 | elif key == 'x': 70 | self.x = value 71 | elif key == 'y': 72 | self.y = value 73 | else: 74 | raise ValueError('{} is an invalid coordinate index'.format(key)) 75 | 76 | def __add__(self, other): 77 | """ Treats coordinates as vectors, performs vector addition """ 78 | other_temp = XY(other) 79 | temp_x = self.x + other_temp.x 80 | temp_y = self.y + other_temp.y 81 | return XY([temp_x, temp_y]) 82 | 83 | def __radd__(self, other): 84 | """ Just flip the order and add """ 85 | return self.__add__(other) 86 | 87 | def __mul__(self, other): 88 | """ Performs element-wise product. If a scalar is given, scales coordinate vector """ 89 | if isinstance(other, numbers.Real): 90 | return XY([self.x * other, self.y * other]) 91 | else: 92 | temp = XY(other) 93 | return XY([self.x * temp.x, self.y * temp.y]) 94 | 95 | def __rmul__(self, other): 96 | """ Just flip the order and multiply """ 97 | return self.__mul__(other) 98 | 99 | def __sub__(self, other): 100 | """ Treats coordinates as vectors, performs subtraction """ 101 | temp = XY(other) 102 | return self + (temp*-1) 103 | 104 | def __rsub__(self, other): 105 | """ Just flip the order and subtract """ 106 | return -1*(self.__sub__(XY(other))) 107 | 108 | """ Getters and Setters """ 109 | 110 | @property 111 | def x(self): 112 | return round(self._x * self._res, 3) 113 | 114 | @x.setter 115 | def x(self, value): 116 | temp = round(value / self._res) # Find location of provided coordinate on grid 117 | self._x = int(temp) # Force the coordinate to be an int 118 | 119 | @property 120 | def y(self): 121 | return round(self._y * self._res, 3) 122 | 123 | @y.setter 124 | def y(self, value): 125 | temp = round(value / self._res) # Find location of provided coordinate on grid 126 | self._y = int(temp) # Force the coordinate to be an int 127 | 128 | @property 129 | def xy(self): 130 | return [self.x, self.y] 131 | 132 | @xy.setter 133 | def xy(self, xy): 134 | self.x = xy[0] 135 | self.y = xy[1] 136 | 137 | """ Utility Functions """ 138 | 139 | def export_locations(self): 140 | """ For now just returns a dict of the coordinates """ 141 | return { 142 | 'x': [self.x], 143 | 'y': [self.y] 144 | } 145 | 146 | def shift_origin(self, origin=(0, 0), orient='R0'): 147 | transform = Mt(orient) # Convert the rotation string to a matrix transformation 148 | # Apply the transformation to the coordinate 149 | new_xy = np.transpose(np.matmul(transform, np.transpose(np.asarray(self.xy)))) 150 | # Convert to XY coordinates and return shifted coordinate 151 | return XY(new_xy) + XY(origin) 152 | 153 | def __lt__(self, other): 154 | """Used to sort XY in min heap""" 155 | return self.x < other.x 156 | -------------------------------------------------------------------------------- /ACG/VirtualInst.py: -------------------------------------------------------------------------------- 1 | from ACG.VirtualObj import VirtualObj 2 | from ACG.XY import XY 3 | 4 | from typing import Optional, Tuple, Union 5 | point_type = Union[float, int] 6 | coord_type = Union[Tuple[point_type, point_type], XY] 7 | 8 | 9 | class VirtualInst(VirtualObj): 10 | """ 11 | A class to enable movement/access of low level instances without directly accessing the master 12 | class 13 | """ 14 | edges = ('l', 'b', 'r', 't') 15 | vertices = ('ll', 'lr', 'ur', 'ul', 'c', 'cl', 'cb', 'cr', 'ct') 16 | valid_orientation = ('R0', 'MX', 'MY', 'R180') 17 | 18 | def __init__(self, master, origin=(0, 0), orient='R0', inst_name=None): 19 | VirtualObj.__init__(self) 20 | 21 | # Init internal properties 22 | self._origin = None 23 | self._orient = None 24 | 25 | # Init local variables 26 | self.master = master 27 | self.origin = origin 28 | self.orient = orient 29 | self.inst_name = inst_name 30 | self.loc = {} 31 | 32 | # Init locations 33 | self.export_locations() 34 | 35 | def __repr__(self): 36 | temp = 'VirtualInst(master={}, origin={}, orient={})' 37 | return temp.format(self.master.__class__.__name__, self.origin, self.orient) 38 | 39 | def __str__(self): 40 | temp = '{}:\n\torigin: {} \n\torient: {}' 41 | return temp.format(self.master.__class__.__name__, self.origin, self.orient) 42 | 43 | """ Properties """ 44 | 45 | @property 46 | def origin(self) -> XY: 47 | return self._origin 48 | 49 | @origin.setter 50 | def origin(self, xy: coord_type) -> None: 51 | self._origin = XY(xy) # feed it into XY class to check/condition input 52 | 53 | @property 54 | def orient(self) -> str: 55 | return self._orient 56 | 57 | @orient.setter 58 | def orient(self, value: str): 59 | if value in VirtualInst.valid_orientation: 60 | self._orient = value 61 | else: 62 | raise ValueError('{} is not a valid orientation'.format(value)) 63 | 64 | """ Utility Methods """ 65 | 66 | def export_locations(self) -> dict: 67 | """ Recursively shift all of the elements in the location dictionary """ 68 | self.loc = {} 69 | try: 70 | old_db = self.master.export_locations() 71 | except AttributeError: 72 | print(f"{self.master.__class__.__name__} is not an ACG class, and does not have a location dict") 73 | old_db = {} 74 | for key in old_db: 75 | # Iterate over all relevant locations in master 76 | if isinstance(old_db[key], list): 77 | # If its a list, init an empty list and iterate 78 | self.loc[key] = [] 79 | for elem in old_db[key]: 80 | self.loc[key].append(elem.shift_origin(origin=self.origin, orient=self.orient)) 81 | elif old_db[key] is None: 82 | print('{} is not a valid location object'.format(key)) 83 | print(old_db[key]) 84 | else: 85 | # If its not a list 86 | self.loc[key] = old_db[key].shift_origin(origin=self.origin, orient=self.orient) 87 | return self.loc 88 | 89 | def move(self, origin=None, orient=None) -> 'VirtualInst': 90 | """ Set the origin and orientation to new values """ 91 | if origin is not None: 92 | self.origin = origin 93 | if orient is not None: 94 | self.orient = orient 95 | # Update locations 96 | self.export_locations() 97 | return self 98 | 99 | def shift_origin(self, origin=None, orient=None) -> 'VirtualInst': 100 | """ Moves the instance origin to the provided coordinates and performs transformation""" 101 | if origin is not None: 102 | new_origin = self.origin + XY(origin) 103 | self.move(new_origin, orient) # Move the block 104 | else: 105 | self.move(origin) 106 | return self 107 | 108 | def align(self, 109 | target_handle: str, 110 | target_rect: VirtualObj = None, 111 | ref_rect: VirtualObj = None, 112 | ref_handle: str = None, 113 | align_opt: Tuple[bool, bool] = (True, True), 114 | offset: coord_type = (0, 0) 115 | ) -> 'VirtualInst': 116 | """ Moves the instance to co-locate target rect handle and reference rect handle """ 117 | if target_rect is None: 118 | # If an explicit target is not provided, assume that the boundary should be used 119 | target_rect = self.loc['bnd'] 120 | 121 | if (target_handle in VirtualInst.edges) or (ref_handle in VirtualInst.edges): 122 | # If you provided an edge handle instead of a corner handle throw an error 123 | ValueError('Please use the align_edge method to align edges') 124 | 125 | if (ref_rect is not None) and (ref_handle in ref_rect.loc): 126 | # if a reference rectangle and handle are provided 127 | diff = target_rect.loc[target_handle] - ref_rect.loc[ref_handle] 128 | diff -= XY(offset) 129 | else: 130 | # otherwise align only to offset coordinates 131 | diff = target_rect.loc[target_handle] - XY(offset) 132 | 133 | # if the corresponding align opt is true, shift the origin appropriately 134 | if align_opt[0]: 135 | self.origin.x -= diff.x 136 | if align_opt[1]: 137 | self.origin.y -= diff.y 138 | # Update locations 139 | self.export_locations() 140 | return self 141 | -------------------------------------------------------------------------------- /ACG/PrimitiveUtil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ######################################################################################################################## 3 | # 4 | # Copyright (c) 2014, Regents of the University of California 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 8 | # following conditions are met: 9 | # 10 | # 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 11 | # disclaimer. 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 13 | # following disclaimer in the documentation and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 21 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | # 23 | ######################################################################################################################## 24 | 25 | """ 26 | Utility functions 27 | """ 28 | 29 | __author__ = "Jaeduk Han" 30 | __maintainer__ = "Jaeduk Han" 31 | __email__ = "jdhan@eecs.berkeley.edu" 32 | __status__ = "Prototype" 33 | 34 | import numpy as np 35 | 36 | # TODO: yaml (refer to GridDB.py), skill, matplotlib export 37 | # TODO: path, label support 38 | 39 | #aux functions 40 | def format_float(value, res): 41 | """ 42 | Format float numbers for pretty printing 43 | 44 | Parameters 45 | ---------- 46 | value : float 47 | number to be printed 48 | res : float 49 | resolution 50 | 51 | Returns 52 | ------- 53 | str 54 | """ 55 | precision = int(np.log10(1 / res)) + 1 56 | fstr = "%." + str(precision) + "f" 57 | return fstr % value 58 | 59 | 60 | def Mt(transform): 61 | """ 62 | Get transform matrix 63 | 64 | Parameters 65 | ---------- 66 | transform : str 67 | transform parameter. possible values are 'R0', 'MX', 'MY', 'MXY', and 'R180' 68 | 69 | Returns 70 | ------- 71 | np.array([[int, int], [int, int]]) 72 | transform matrix 73 | """ 74 | if transform=='R0': 75 | return np.array([[1, 0], [0, 1]]) 76 | if transform=='MX': 77 | return np.array([[1, 0], [0, -1]]) 78 | if transform=='MY': 79 | return np.array([[-1, 0], [0, 1]]) 80 | if transform=='MXY': #mirror to y=x line 81 | return np.array([[0, 1], [1, 0]]) 82 | if transform=='R180': 83 | return np.array([[-1, 0], [0, -1]]) 84 | 85 | 86 | def Mtinv(transform): 87 | """ 88 | Get inverse of transform matrix 89 | 90 | Parameters 91 | ---------- 92 | transform : str 93 | transform parameter. possible values are 'R0', 'MX', 'MY', 'MXY', and 'R180' 94 | 95 | Returns 96 | ------- 97 | np.array([[int, int], [int, int]]) 98 | inverse of transform matrix 99 | """ 100 | if transform=='R0': 101 | return np.array([[1, 0], [0, 1]]) 102 | if transform=='MX': 103 | return np.array([[1, 0], [0, -1]]) 104 | if transform=='MY': 105 | return np.array([[-1, 0], [0, 1]]) 106 | if transform=='MXY': #mirror to y=x line 107 | return np.array([[0, 1], [1, 0]]) 108 | if transform=='R180': 109 | return np.array([[-1, 0], [0, -1]]) 110 | 111 | def Md(direction): 112 | """ 113 | Get direction/projection matrix 114 | 115 | Parameters 116 | ---------- 117 | direction : str 118 | direction/projection parameter. Possible values are 'left', 'right', 'top', 'bottom', 'omni', 'x', 'y'. 119 | 120 | Returns 121 | ------- 122 | np.array([[int, int], [int, int]]) 123 | directional matrix 124 | """ 125 | if direction== 'left': 126 | return np.array([[-1, 0], [0, 0]]) 127 | if direction== 'right': 128 | return np.array([[1, 0], [0, 0]]) 129 | if direction== 'top': 130 | return np.array([[0, 0], [0, 1]]) 131 | if direction== 'bottom': 132 | return np.array([[0, 0], [0, -1]]) 133 | if direction== 'omni': 134 | return np.array([[1, 0], [0, 1]]) 135 | if direction== 'x': 136 | return np.array([[1, 0], [0, 0]]) 137 | if direction== 'y': 138 | return np.array([[0, 0], [0, 1]]) 139 | 140 | 141 | def locate_xy(xy0, xy1, location): 142 | """ 143 | Find a corresponding xy coordinate from location parameters 144 | 145 | Parameters 146 | ---------- 147 | xy0 : np.array([float, float]) 148 | first coordinate 149 | xy1 : np.array([float, float]) 150 | second coordinate 151 | location : str 152 | direction/projection parameter. Possible values are 'lowerLeft', 'upperRight', 'centerCenter', ... 153 | 154 | Returns 155 | ------- 156 | np.array([float, float]) 157 | resulting coordinate 158 | """ 159 | if location == 'lowerLeft': 160 | return np.array(xy0) 161 | if location == 'lowerRight': 162 | return np.array([xy1[0], xy0[1]]) 163 | if location == 'upperLeft': 164 | return np.array([xy0[0], xy1[1]]) 165 | if location == 'upperRight': 166 | return np.array(xy1) 167 | if location == 'centerCenter': 168 | return np.array(0.5*xy0+0.5*xy1) 169 | -------------------------------------------------------------------------------- /ACG/Track.py: -------------------------------------------------------------------------------- 1 | from ACG.XY import XY 2 | from typing import Dict, TYPE_CHECKING 3 | if TYPE_CHECKING: 4 | from bag.layout.routing.grid import RoutingGrid 5 | 6 | 7 | class TrackManager: 8 | """ 9 | A class that enables users to create tracks and use them as references for routing 10 | """ 11 | def __init__(self): 12 | self.tracks: Dict[Track] = {} 13 | 14 | def __getitem__(self, item) -> 'Track': 15 | """ Use dictionary syntax to access a specific track instance """ 16 | return self.tracks[item] 17 | 18 | def __str__(self): 19 | track_str = "" 20 | for name, track in self.tracks.items(): 21 | track_str += str(name) + ' ' + str(track) 22 | return track_str 23 | 24 | @classmethod 25 | def from_routing_grid(cls, grid: 'RoutingGrid'): 26 | """ 27 | Generates a track manager object from the current grid 28 | 29 | Parameters 30 | ---------- 31 | grid 32 | RoutingGrid object used as reference to build all of the desired tracks 33 | 34 | Returns 35 | ------- 36 | TrackManager 37 | Generated TrackManager object from the provided RoutingGrid 38 | """ 39 | track_manager = cls() 40 | for layer_id in grid.sp_tracks: 41 | name = grid.tech_info.get_layer_name(layer_id) 42 | spacing = grid.sp_tracks[layer_id] * grid.resolution 43 | dim = grid.dir_tracks[layer_id] 44 | track_manager.add_track(name=name, dim=dim, spacing=spacing) 45 | return track_manager 46 | 47 | def add_track(self, name, dim, spacing, origin=0): 48 | """ 49 | Adds a track to the database. 50 | 51 | Parameters 52 | ---------- 53 | name 54 | name to associate with the added track 55 | dim 56 | 'x' or 'y' for the desired routing direction 57 | spacing 58 | space between tracks 59 | origin 60 | coordinate to place the zero track 61 | """ 62 | if name in self.tracks: 63 | raise ValueError(f"Track {name} already exists in the db") 64 | else: 65 | self.tracks[name] = Track(dim=dim, spacing=spacing, origin=origin) 66 | 67 | 68 | class Track: 69 | """ 70 | A class for creating consistently spaced reference lines 71 | """ 72 | def __init__(self, dim, spacing, origin=0): 73 | """ 74 | dim: str 75 | x or y. Refers to the direction that the tracks span 76 | spacing: float 77 | the amount of space between any two tracks 78 | origin: float 79 | the single x or y coordinate that determines the location of Track.get_track(0) 80 | """ 81 | 82 | # Init local property variables 83 | self._dim = None 84 | self._spacing = None 85 | self._origin = None 86 | self._res = .001 87 | 88 | # Store provided params with property setters 89 | self.dim = dim # refers to the direction that the tracks span 90 | self.origin = origin # coordinate of track 0 91 | self.spacing = spacing # distance between tracks 92 | 93 | """ Magic Methods """ 94 | def __call__(self, num): 95 | return self.get_track(num) 96 | 97 | def __str__(self): 98 | return f"Track:\n\tdim: {self.dim}\n\tspacing: {self.spacing}\n" 99 | 100 | """ Properties """ 101 | @property 102 | def dim(self): 103 | return self._dim 104 | 105 | @dim.setter 106 | def dim(self, direction): 107 | if direction is 'x': 108 | self._dim = 'x' 109 | elif direction is 'y': 110 | self._dim = 'y' 111 | else: 112 | raise ValueError('Provided direction is invalid, must be x or y') 113 | 114 | @property 115 | def spacing(self): 116 | return self._spacing * self._res 117 | 118 | @spacing.setter 119 | def spacing(self, value): 120 | temp = round(value / self._res) # Find location of provided spacing on grid 121 | self._spacing = int(temp) # Force the coordinate to be an int 122 | 123 | @property 124 | def origin(self): 125 | return self._origin * self._res 126 | 127 | @origin.setter 128 | def origin(self, value): 129 | temp = round(value / self._res) # Find location of provided spacing on grid 130 | self._origin = int(temp) # Force the coordinate to be an int 131 | 132 | """ Utility Methods """ 133 | 134 | def get_track(self, num) -> XY: 135 | """ Returns [x, y] coordinates of desired track # """ 136 | distance = self.spacing * num 137 | 138 | if self.dim is 'x': 139 | return XY([self.origin + distance, 0]) 140 | elif self.dim is 'y': 141 | return XY([0, self.origin + distance]) 142 | 143 | def align(self, ref_rect, ref_handle, num=0, offset=0): 144 | """ Aligns the provided track number to handle of reference rectangle """ 145 | ref_loc = ref_rect.loc[ref_handle] # grab coordinates of reference location 146 | curr_loc = self.get_track(num) # grab coordinates of track location 147 | 148 | if self.dim is 'x': 149 | diff_x = curr_loc.x - ref_loc.x - offset # calculate x dimension difference 150 | self.origin -= diff_x 151 | elif self.dim is 'y': 152 | diff_y = curr_loc.y - ref_loc.x - offset # calculate y dimension difference 153 | self.origin -= diff_y 154 | 155 | def stretch(self, track_num, ref_rect, ref_handle, offset=0): 156 | """ Stretches the track spacing to co-locate track and handle """ 157 | ref_loc = ref_rect[ref_handle] 158 | curr_loc = self.get_track(track_num) 159 | 160 | if self.dim is 'x': 161 | diff_x = curr_loc.x - ref_loc.x - offset 162 | self.spacing -= diff_x 163 | elif self.dim is 'y': 164 | diff_y = curr_loc.y - ref_loc.y - offset 165 | self.spacing -= diff_y 166 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # ACG documentation build configuration file, created by 5 | # sphinx-quickstart on Wed Jun 6 14:22:25 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | from recommonmark.parser import CommonMarkParser 23 | 24 | sys.path.insert(0, os.path.abspath('../../.')) 25 | os.environ['ACG_TECH'] = os.path.abspath('../../examples/tech.yaml') 26 | 27 | # -- General configuration ------------------------------------------------ 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ['sphinx.ext.autodoc', 37 | 'sphinx.ext.intersphinx', 38 | 'sphinx.ext.todo', 39 | 'sphinx.ext.coverage', 40 | 'sphinx.ext.imgmath', 41 | 'sphinx.ext.viewcode', 42 | 'sphinx.ext.githubpages', 43 | 'sphinx.ext.napoleon', 44 | 'recommonmark'] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix(es) of source filenames. 50 | # You can specify multiple suffix as a list of string: 51 | # 52 | source_suffix = ['.rst', '.md'] 53 | # source_suffix = '.rst' 54 | # source_parsers = { 55 | # '.md': CommonMarkParser 56 | # } 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # General information about the project. 62 | project = 'ACG' 63 | copyright = '2018, Pavan Bhargava' 64 | author = 'Pavan Bhargava' 65 | 66 | # The version info for the project you're documenting, acts as replacement for 67 | # |version| and |release|, also used in various other places throughout the 68 | # built documents. 69 | # 70 | # The short X.Y version. 71 | version = '0.1.0' 72 | # The full version, including alpha/beta/rc tags. 73 | release = '0.1.0' 74 | 75 | # The language for content autogenerated by Sphinx. Refer to documentation 76 | # for a list of supported languages. 77 | # 78 | # This is also used if you do content translation via gettext catalogs. 79 | # Usually you set "language" from the command line for these cases. 80 | language = None 81 | 82 | # List of patterns, relative to source directory, that match files and 83 | # directories to ignore when looking for source files. 84 | # This patterns also effect to html_static_path and html_extra_path 85 | exclude_patterns = [] 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # If true, `todo` and `todoList` produce output, else they produce nothing. 91 | todo_include_todos = True 92 | 93 | # -- Options for HTML output ---------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | # 98 | # html_theme = 'classic' 99 | html_theme = "sphinx_rtd_theme" 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | # 105 | # html_theme_options = {} 106 | 107 | # Add any paths that contain custom static files (such as style sheets) here, 108 | # relative to this directory. They are copied after the builtin static files, 109 | # so a file named "default.css" will overwrite the builtin "default.css". 110 | html_static_path = ['_static'] 111 | 112 | # Custom sidebar templates, must be a dictionary that maps document names 113 | # to template names. 114 | # 115 | # This is required for the alabaster theme 116 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 117 | # html_sidebars = { 118 | # '**': [ 119 | # 'about.html', 120 | # 'navigation.html', 121 | # 'relations.html', # needs 'show_related': True theme option to display 122 | # 'searchbox.html', 123 | # 'donate.html', 124 | # ] 125 | # } 126 | 127 | 128 | # -- Options for HTMLHelp output ------------------------------------------ 129 | 130 | # Output file base name for HTML help builder. 131 | htmlhelp_basename = 'ACGdoc' 132 | 133 | # -- Options for LaTeX output --------------------------------------------- 134 | 135 | latex_elements = { 136 | # The paper size ('letterpaper' or 'a4paper'). 137 | # 138 | # 'papersize': 'letterpaper', 139 | 140 | # The font size ('10pt', '11pt' or '12pt'). 141 | # 142 | # 'pointsize': '10pt', 143 | 144 | # Additional stuff for the LaTeX preamble. 145 | # 146 | # 'preamble': '', 147 | 148 | # Latex figure (float) alignment 149 | # 150 | # 'figure_align': 'htbp', 151 | } 152 | 153 | # Grouping the document tree into LaTeX files. List of tuples 154 | # (source start file, target name, title, 155 | # author, documentclass [howto, manual, or own class]). 156 | latex_documents = [ 157 | (master_doc, 'ACG.tex', 'ACG Documentation', 158 | 'Pavan Bhargava', 'manual'), 159 | ] 160 | 161 | # -- Options for manual page output --------------------------------------- 162 | 163 | # One entry per manual page. List of tuples 164 | # (source start file, name, description, authors, manual section). 165 | man_pages = [ 166 | (master_doc, 'acg', 'ACG Documentation', 167 | [author], 1) 168 | ] 169 | 170 | # -- Options for Texinfo output ------------------------------------------- 171 | 172 | # Grouping the document tree into Texinfo files. List of tuples 173 | # (source start file, target name, title, author, 174 | # dir menu entry, description, category) 175 | texinfo_documents = [ 176 | (master_doc, 'ACG', 'ACG Documentation', 177 | author, 'ACG', 'One line description of project.', 178 | 'Miscellaneous'), 179 | ] 180 | 181 | # Example configuration for intersphinx: refer to the Python standard library. 182 | intersphinx_mapping = {'https://docs.python.org/': None} 183 | -------------------------------------------------------------------------------- /ACG/Via.py: -------------------------------------------------------------------------------- 1 | from ACG.VirtualObj import VirtualObj 2 | from ACG.Rectangle import Rectangle 3 | from ACG import tech as tech_info 4 | from typing import Optional, List, Tuple 5 | 6 | 7 | class ViaStack(VirtualObj): 8 | """ 9 | A class enabling flexible creation/manipulation of ACG via stacks. There are two main ways that vias 10 | can be created: 11 | 12 | 1. A bbox is created representing the area that a via is allowed to occupy, and then it is sent to bag 13 | to fill with vias and enclosures 14 | 15 | 2. Rectangles are manually created to generate the desired via properties. 16 | 17 | Generally if the user does not call any special properties, the first option will be used to ensure 18 | DRC compliance. Note that the added options may or may not satisfy DRC constraints. 19 | """ 20 | 21 | def __init__(self, 22 | rect1: Rectangle, 23 | rect2: Rectangle, 24 | size=(None, None), 25 | extend=False, 26 | ): 27 | """ 28 | Creates a Via based on the overlap region between the two provided rectangles. 29 | 30 | Parameters 31 | ---------- 32 | rect1 : Rectangle 33 | One of the Rectangles to be connected by this via stack 34 | rect2 : Rectangle 35 | One of the Rectangles to be connected by this via stack 36 | size : (int, int) 37 | Tuple representing the via array size in the (x, y) dimension 38 | extend : bool 39 | Tuple representing whether the overlap region can be extended in the (x, y) dimension to meet the 40 | via enclosure rules 41 | """ 42 | 43 | VirtualObj.__init__(self) 44 | 45 | # Variable initialization 46 | self.rect1 = rect1 47 | self.rect2 = rect2 48 | self.size = size 49 | self.metal_pairs = [] # List containing all metal layers to be connected by vias 50 | self.extend = extend # Flag for top-level bag code to extend enclosure beyond provided overlap 51 | self.loc = { 52 | 'top': None, 53 | 'bottom': None, 54 | 'overlap': None, 55 | 'rect_list': [] 56 | } 57 | 58 | # Get process specific data 59 | self.tech_prop = tech_info.tech_info['metal_tech'] 60 | self.routing = self.tech_prop['routing'] 61 | self.metals = self.tech_prop['metals'] 62 | self.vias = self.tech_prop['vias'] 63 | self.dir = self.tech_prop['dir'] 64 | 65 | # Generate the actual via stack 66 | self.compute_via() 67 | 68 | def export_locations(self) -> dict: 69 | return self.loc 70 | 71 | def shift_origin(self, origin=(0, 0), orient='R0'): 72 | self.loc['overlap'] = self.loc['overlap'].shift_origin(origin=origin, orient=orient) 73 | 74 | def compute_via(self): 75 | """ 76 | Takes the stored rectangles and creates the overlap region necessary to meet the user constraints. 77 | BAG then uses this overlap region to generate the actual vias. 78 | 79 | Via creation algorithm 80 | ---------------------- 81 | 1) Determine all metal layers to be drawn in the via stack 82 | 2) Determine the overlap region to be filled with vias 83 | 3) If a via size is given, adjust the overlap region to meet the constraints 84 | 3) Fill in bottom metal routing direction to satisfy BAG's routing requirements 85 | """ 86 | self.find_metal_pairs() 87 | self.find_overlap() 88 | if self.size != (None, None): 89 | self.set_via_size() 90 | self.bot_dir = self.dir[self.routing.index(self.loc['bottom'].layer)] 91 | 92 | def find_metal_pairs(self): 93 | """ 94 | Creates an ordered list of metal pairs required to generate a via stack between rect1 and rect2 95 | 96 | Metal ordering algorithm 97 | ------------------------ 98 | 1) Map each rectangle's layer to index in the metal stack 99 | 2) Determine which rect is lower/higher in the stack based on the index 100 | 3) Add each metal pair in the stack between rect1 and rect2 to the metal_pair list, starting with the lower 101 | rectangle and traversing up the metal stack 102 | """ 103 | # 1) Map each rectangle layer to index in the metal stack 104 | i1 = self.metals[self.rect1.layer]['index'] 105 | i2 = self.metals[self.rect2.layer]['index'] 106 | 107 | # 2) Determine which rect is lower/higher in the stack 108 | if i2 > i1: 109 | self.loc['bottom'] = self.rect1 110 | self.loc['top'] = self.rect2 111 | else: 112 | self.loc['bottom'] = self.rect2 113 | self.loc['top'] = self.rect1 114 | 115 | # 3) Add each metal pair between rect1 and rect2 to the metal_pair list 116 | bot_layer = self.loc['bottom'].layer 117 | while True: 118 | # Try to find the metal layer that the current bot layer connects to 119 | try: 120 | top_layer = self.metals[bot_layer]['connect_to'] 121 | except KeyError: 122 | raise ValueError('Could not complete via stack from {} to {}'.format(self.loc['top'].layer, 123 | self.loc['bottom'].layer)) 124 | self.metal_pairs.append((bot_layer, top_layer, self.dir[self.routing.index(bot_layer)])) 125 | if top_layer == self.loc['top'].layer: 126 | # If we have made it from the bottom to the top of the via stack, break out of the loop 127 | break 128 | else: 129 | # Otherwise continue traversing the via stack 130 | bot_layer = top_layer 131 | 132 | def find_overlap(self): 133 | """ 134 | Takes the bottom and top rectangles and stores the rectangle representing their overlapping region. The overlap 135 | region is used to determine the area to be filled with vias. 136 | """ 137 | lower_rect = self.loc['bottom'] 138 | upper_rect = self.loc['top'] 139 | 140 | self.loc['overlap'] = upper_rect.get_overlap(lower_rect) 141 | 142 | def set_via_size(self): 143 | """ 144 | If the user provided a via array size, adjust the overlap region to fit the desired number of vias. Enable 145 | extend to make BAG compute the proper enclosure size 146 | 147 | Overlap adjustment algorithm 148 | ---------------------------- 149 | 1) Determine the highest metal pair in via stack and grab its tech info 150 | 2) For size constraints, set the overlap region to fit the desired number of vias 151 | """ 152 | # 1) Get tech info for the highest metal pair in the via stack 153 | bot_layer, top_layer, _ = self.metal_pairs[-1] 154 | via_prop = self.vias['V' + bot_layer + '_' + top_layer] 155 | 156 | # 2) Compute overlap required to fit via of given size 157 | self.loc['overlap'].set_dim('x', via_prop['via_size'] + (self.size[0] - 1) * via_prop['via_pitch']) 158 | self.loc['overlap'].set_dim('y', via_prop['via_size'] + (self.size[1] - 1) * via_prop['via_pitch']) 159 | self.extend = True 160 | 161 | def remove_enclosure(self) -> 'Via': 162 | """ 163 | This method removes any metal enclosure on the generated via. Note that this will not be DRC clean 164 | unless minimum area and minimum enclosure rules are satisfied by other routes you create 165 | """ 166 | raise NotImplemented('Remove enclosure is currently not supported with via stacks') 167 | 168 | 169 | class Via(VirtualObj): 170 | """ 171 | A class that wraps the functionality of adding primitive via types to the layout 172 | """ 173 | 174 | # Get process specific data 175 | tech_prop = tech_info.tech_info['metal_tech'] 176 | routing = tech_prop['routing'] 177 | metals = tech_prop['metals'] 178 | vias = tech_prop['vias'] 179 | dir = tech_prop['dir'] 180 | 181 | def __init__(self, 182 | via_id: str, 183 | bbox: Rectangle, 184 | size: Tuple[int, int] = (1, 1), 185 | ): 186 | """ 187 | Creates a Via based on the overlap region between the two provided rectangles. 188 | 189 | Parameters 190 | ---------- 191 | via_id: str 192 | String representing the via type to be drawn. 193 | size : (int, int) 194 | Tuple representing the via array size in the (x, y) dimension 195 | """ 196 | VirtualObj.__init__(self) 197 | 198 | # User variable initialization 199 | self.bbox = bbox 200 | self.size = size 201 | self.loc = { 202 | 'overlap': self.bbox, 203 | } 204 | 205 | # Via primitive properties 206 | self.via_id = via_id 207 | self.location = bbox.center.xy 208 | self.sp_rows: float = 0 209 | self.sp_cols: float = 0 210 | self.enc_bot: List[float] = [0, 0, 0, 0] 211 | self.enc_top: List[float] = [0, 0, 0, 0] 212 | self.orient: str = 'R0' 213 | 214 | # Generate the actual via 215 | self.compute_via() 216 | 217 | @property 218 | def num_rows(self) -> int: 219 | return self.size[0] 220 | 221 | @property 222 | def num_cols(self) -> int: 223 | return self.size[1] 224 | 225 | @classmethod 226 | def from_metals(cls, 227 | rect1: Rectangle, 228 | rect2: Rectangle, 229 | size: Tuple[int, int] = (None, None) 230 | ) -> "Via": 231 | """ Generates a via instance from two rectangles """ 232 | via_id0 = 'V' + rect1.layer + '_' + rect2.layer 233 | via_id1 = 'V' + rect2.layer + '_' + rect1.layer 234 | if via_id0 in cls.vias: 235 | return cls(bbox=rect1.get_overlap(rect2), 236 | via_id=via_id0, 237 | size=size) 238 | elif via_id1 in cls.vias: 239 | return cls(bbox=rect2.get_overlap(rect1), 240 | via_id=via_id1, 241 | size=size) 242 | else: 243 | raise ValueError(f"A single via cannot be created between {rect1.layer} and {rect2.layer}") 244 | 245 | def export_locations(self) -> dict: 246 | return self.loc 247 | 248 | def shift_origin(self, origin=(0, 0), orient='R0'): 249 | self.loc['overlap'] = self.loc['overlap'].shift_origin(origin=origin, orient=orient) 250 | 251 | def compute_via(self): 252 | """ 253 | This method extracts the expected via spacing and enclosure based on the provided via id 254 | """ 255 | tech = self.vias[self.via_id] 256 | self.sp_cols = tech['via_space'] 257 | self.sp_rows = tech['via_space'] 258 | self.enc_bot = [tech['uniform_enclosure']] * 4 259 | self.enc_top = [tech['uniform_enclosure']] * 4 260 | 261 | def remove_enclosure(self) -> 'Via': 262 | """ 263 | This method removes any metal enclosure on the generated via. Note that this will not be DRC clean 264 | unless minimum area and minimum enclosure rules are satisfied by other routes you create 265 | """ 266 | tech = self.vias[self.via_id] 267 | self.enc_bot = [tech['zero_enclosure']] * 4 268 | self.enc_top = [tech['zero_enclosure']] * 4 269 | return self 270 | 271 | def set_enclosure(self, 272 | enc_bot=None, 273 | enc_top=None, 274 | type=None 275 | ) -> 'Via': 276 | """ 277 | This method enables you to manually set the enclosure sizing for the top and bottom metal layers 278 | 279 | Parameters 280 | ---------- 281 | enc_bot : List[float] 282 | enclosure size of the left, bottom, right, top edges of the bottom layer 283 | enc_top : List[float] 284 | enclosure size of the left, bottom, right, top edges of the top layer 285 | type : str 286 | TODO: Enables easy selection between asymmetric enclosure styles 287 | """ 288 | if enc_bot: 289 | self.enc_bot = enc_bot 290 | if enc_top: 291 | self.enc_top = enc_top 292 | return self 293 | -------------------------------------------------------------------------------- /ACG/AyarDesignManager.py: -------------------------------------------------------------------------------- 1 | import os 2 | import importlib 3 | from bag.io import read_yaml 4 | from bag.layout.routing import RoutingGrid 5 | from bag.layout.template import TemplateDB 6 | # TB imports 7 | from bag.data import load_sim_results, save_sim_results, load_sim_file 8 | 9 | 10 | class AyarDesignManager: 11 | """ 12 | Class that oversees the creation of layouts, schematics, testbenches, and simulations. Overrides DesignMaster for 13 | more intuitive yaml file organization and handles the RoutingGrid in grid-free layouts 14 | """ 15 | 16 | def __init__(self, bprj, spec_file, gds_layermap=''): 17 | self.prj = bprj 18 | self.tdb = None # templateDB instance for layout creation 19 | self.impl_lib = None # Virtuoso library where generated cells are stored 20 | self.cell_name_list = None # list of names for each created cell 21 | self.specs = read_yaml(spec_file) 22 | 23 | # Initialize self.tdb with appropriate templateDB instance 24 | self.make_tdb(gds_layermap) 25 | 26 | """ 27 | GENERATION METHODS - call these methods to generate and simulate your designs 28 | """ 29 | 30 | def run_flow(self): 31 | """ 32 | Override and call this method to specify your design procedure when subclassing 33 | """ 34 | pass 35 | 36 | def generate_layout(self, layout_params_list=None, cell_name_list=None): 37 | """ 38 | Generates a batch of layouts with the layout package/class in the spec file with parameters set by 39 | layout_params_list and names them according to cell_name_list. Each dict in the layout_params_list creates a 40 | new layout 41 | 42 | layout_params_list : :obj:'list' of :obj:'dict' 43 | list of parameter dicts to be applied to the specified layout class 44 | cell_name_list : :obj:'list' of :obj:'str' 45 | list of names to be applied to each implementation of the layout class 46 | """ 47 | # If no list is provided, extract parameters from the provided spec file 48 | if layout_params_list is None: 49 | if 'layout_params' in self.specs: 50 | layout_params_list = [self.specs['layout_params']] 51 | else: 52 | layout_params_list = [self.specs['dsn_params']] 53 | if cell_name_list is None: 54 | cell_name_list = [self.specs['impl_cell']] 55 | # Reformat provided lists 56 | if type(layout_params_list) is not list: 57 | layout_params_list = [layout_params_list] 58 | if type(cell_name_list) is not list: 59 | cell_name_list = [cell_name_list] 60 | 61 | print('Generating Layout') 62 | cls_package = self.specs['layout_package'] 63 | cls_name = self.specs['layout_class'] 64 | 65 | lay_module = importlib.import_module(cls_package) 66 | temp_cls = getattr(lay_module, cls_name) 67 | 68 | temp_list = [] 69 | for lay_params in layout_params_list: 70 | template = self.tdb.new_template(params=lay_params, temp_cls=temp_cls, debug=False) 71 | temp_list.append(template) 72 | 73 | self.tdb.batch_layout(self.prj, temp_list, cell_name_list) 74 | 75 | def generate_schematic(self, sch_params_list=None, cell_name_list=None): 76 | """ 77 | Generates a batch of schematics specified by sch_params_list and names them according to cell_name_list. 78 | Each dict in the sch_params_list creates a new schematic 79 | 80 | Parameters 81 | ---------- 82 | sch_params_list : :obj:'list' of :obj:'dict' 83 | parameter dicts to be applied to the specified layout class 84 | cell_name_list : :obj:'list' of :obj:'str' 85 | list of names to be applied to each implementation of the layout class 86 | """ 87 | # If no list is provided, extract parameters from the provided spec file 88 | if sch_params_list is None: 89 | if 'sch_params' in self.specs: 90 | sch_params_list = [self.specs['sch_params']] 91 | else: 92 | sch_params_list = [self.specs['dsn_params']] 93 | if cell_name_list is None: 94 | cell_name_list = [self.specs['impl_cell']] 95 | if type(sch_params_list) is not list: 96 | sch_params_list = [sch_params_list] 97 | if type(cell_name_list) is not list: 98 | cell_name_list = [cell_name_list] 99 | 100 | print('Generating Schematic') 101 | sch_temp_lib = self.specs['sch_temp_lib'] 102 | sch_temp_cell = self.specs['sch_temp_cell'] 103 | impl_lib = self.specs['impl_lib'] 104 | 105 | inst_list, name_list = [], [] 106 | for sch_params, cur_name in zip(sch_params_list, cell_name_list): 107 | dsn = self.prj.create_design_module(sch_temp_lib, sch_temp_cell) 108 | dsn.design(**sch_params) 109 | inst_list.append(dsn) 110 | name_list.append(cur_name) 111 | 112 | self.prj.batch_schematic(impl_lib, inst_list, name_list=name_list) 113 | 114 | def generate_tb(self, tb_params_list=None, tb_name_list=None): 115 | """ 116 | Generates a batch of testbenches specified by tb_params_list and names them according to tb_name_list. 117 | Each dict in tb_params_list creates a new set of tb's 118 | 119 | Parameters 120 | ---------- 121 | tb_params_list : :obj:'list' of :obj:'dict' 122 | list of parameter dicts to be applied to the testbench generator class 123 | tb_name_list : :obj:'list' of :obj:'str' 124 | list of names to be applied to each implementation of the tb class 125 | """ 126 | # If no info is provided, extract parameters from the provided spec file 127 | if tb_params_list is None or tb_name_list is None: 128 | tb_name_list = self.specs['tb_params'].keys() 129 | tb_params_list = self.specs['tb_params'].values() 130 | 131 | print('Generating Testbench') 132 | impl_lib = self.specs['impl_lib'] 133 | impl_cell = self.specs['impl_cell'] 134 | 135 | for info, name in zip(tb_params_list, tb_name_list): 136 | tb_lib = info['tb_lib'] 137 | tb_cell = info['tb_cell'] 138 | tb_sch_params = info['tb_sch_params'] 139 | # If dut lib/cell is provided, override the impl lib/cell 140 | if 'dut_lib' in info and 'dut_cell' in info: 141 | impl_lib = info['dut_lib'] 142 | impl_cell = info['dut_cell'] 143 | 144 | tb_dsn = self.prj.create_design_module(tb_lib, tb_cell) 145 | tb_dsn.design(dut_lib=impl_lib, dut_cell=impl_cell, **tb_sch_params) 146 | tb_dsn.implement_design(impl_lib, top_cell_name=name) 147 | 148 | def simulate(self): 149 | """ 150 | Runs a batch of simulations on the generated TB's. All parameters for simulation are set within the spec file 151 | """ 152 | print('Running Simulation') 153 | impl_lib = self.specs['impl_lib'] 154 | impl_cell = self.specs['impl_cell'] 155 | 156 | results_dict = {} 157 | for tb_impl_cell, info in self.specs['tb_params'].items(): 158 | tb_params = info['tb_sim_params'] 159 | view_name = info['view_name'] 160 | sim_envs = info['sim_envs'] 161 | data_dir = info['data_dir'] 162 | 163 | # setup testbench ADEXL state 164 | print('setting up %s' % tb_impl_cell) 165 | tb = self.prj.configure_testbench(impl_lib, tb_impl_cell) 166 | # set testbench parameters values 167 | for key, val in tb_params.items(): 168 | tb.set_parameter(key, val) 169 | # set config view, i.e. schematic vs extracted 170 | tb.set_simulation_view(impl_lib, impl_cell, view_name) 171 | # set process corners 172 | tb.set_simulation_environments(sim_envs) 173 | # commit changes to ADEXL state back to database 174 | tb.update_testbench() 175 | print('running simulation') 176 | tb.run_simulation() 177 | print('simulation done, load results') 178 | results = load_sim_results(tb.save_dir) 179 | save_sim_results(results, os.path.join(data_dir, '%s.hdf5' % tb_impl_cell)) 180 | results_dict[tb_impl_cell] = results 181 | 182 | print('all simulation done') 183 | return results_dict 184 | 185 | def run_LVS(self, cell_name_list=None): 186 | """ 187 | Runs LVS on a batch of cells contained within the implementation library 188 | 189 | Parameters 190 | ---------- 191 | cell_name_list : :obj:'list' of :obj:'str' 192 | list of strings containing the names of the cells we should run LVS on 193 | """ 194 | if not cell_name_list: 195 | cell_name_list = [self.specs['impl_cell']] 196 | 197 | for cell_name in cell_name_list: 198 | print('Running LVS on {}'.format(cell_name)) 199 | lvs_passed, lvs_log = self.prj.run_lvs(self.impl_lib, cell_name) 200 | if lvs_passed: 201 | print('\n' + 'LVS Clean :)') 202 | else: 203 | print('\n' + 'LVS Incorrect :(') 204 | print('LVS log path: {}'.format(lvs_log)) 205 | 206 | def run_PEX(self, cell_name_list): 207 | """ 208 | Runs PEX on a batch of cells contained within the implementation library 209 | 210 | Parameters 211 | ---------- 212 | cell_name_list : :obj:'list' of :obj:'str' 213 | list of strings containing the names of the cells we should run PEX on 214 | """ 215 | for cell_name in cell_name_list: 216 | print('Running PEX on {}'.format(cell_name)) 217 | pex_passed, pex_log = self.prj.run_rcx(self.impl_lib, cell_name, create_schematic=True) 218 | if pex_passed: 219 | print('\n' + 'PEX Passed :)') 220 | else: 221 | print('\n' + 'PEX Failed :(') 222 | print('PEX log path: {}'.format(pex_log)) 223 | 224 | def load_sim_data(self): 225 | """ 226 | Returns simulation data for all TBs in spec file 227 | """ 228 | results_dict = {} 229 | for name, info in self.specs['tb_params'].items(): 230 | data_dir = info['data_dir'] 231 | fname = os.path.join(data_dir, '%s.hdf5' % name) 232 | print('loading simulation data for %s' % name) 233 | results_dict[name] = load_sim_file(fname) 234 | 235 | print('finish loading data') 236 | return results_dict 237 | 238 | def import_schematic_library(self, lib_name): 239 | """ 240 | Imports a Cadence library containing schematic templates for use in BAG, this must be called if 241 | changes to the schematic were made since the last run 242 | 243 | Parameters 244 | ---------- 245 | lib_name : str 246 | string containing name of the library to be imported 247 | """ 248 | self.prj.import_design_library(lib_name) 249 | 250 | """ 251 | HELPER METHODS - These should not need to be called by any subclass or external routine 252 | """ 253 | 254 | def make_tdb(self, layermap=''): 255 | """ 256 | Makes a new TemplateDB object. If no routing grid parameters are sent in, dummy parameters are used. 257 | """ 258 | self.impl_lib = self.specs['impl_lib'] 259 | if 'routing_grid' in self.specs: 260 | layers = self.specs['routing_grid']['layers'] 261 | spaces = self.specs['routing_grid']['spaces'] 262 | widths = self.specs['routing_grid']['widths'] 263 | bot_dir = self.specs['routing_grid']['bot_dir'] 264 | else: 265 | # Use dummy routing grid settings 266 | layers = [1, 2, 3, 4, 5] 267 | spaces = [0.1, 0.1, 0.1, 0.1, 0.2] 268 | widths = [0.1, 0.1, 0.1, 0.1, 0.2] 269 | bot_dir = 'y' 270 | 271 | routing_grid = RoutingGrid(self.prj.tech_info, layers, spaces, widths, bot_dir) 272 | self.tdb = TemplateDB('template_libs.def', 273 | routing_grid, 274 | self.impl_lib, 275 | use_cybagoa=True, 276 | name_prefix=self.specs.get('name_prefix', ''), 277 | name_suffix=self.specs.get('name_suffix', ''), 278 | prj=self.prj, 279 | gds_lay_file=layermap) 280 | -------------------------------------------------------------------------------- /ACG/Rectangle.py: -------------------------------------------------------------------------------- 1 | from ACG.VirtualObj import VirtualObj 2 | from ACG.XY import XY 3 | from bag.layout.util import BBox 4 | from typing import Tuple, Union, Optional 5 | from ACG import tech as tech_info 6 | coord_type = Union[Tuple[float, float], XY] 7 | 8 | 9 | class Rectangle(VirtualObj): 10 | """ 11 | Creates a better rectangle object with stretch and align capabilities 12 | """ 13 | 14 | """ Constructor Methods """ 15 | 16 | def __init__(self, xy, layer, virtual=False): 17 | """ 18 | xy: [[x0, y0], [x1, y1]] 19 | xy coordinates for ll and ur locations 20 | layer: str 21 | layer name 22 | virtual: Bool 23 | If True, do not draw the rectangle 24 | """ 25 | 26 | VirtualObj.__init__(self) 27 | 28 | # Init internal properties 29 | self._ll = None 30 | self._ur = None 31 | self._res = .001 32 | self._lpp: Optional[Tuple[str, str]] = None 33 | 34 | # Init local variables 35 | self.xy = xy # property setter creates ll and ur coordinates 36 | self.layer = layer 37 | self.virtual: bool = virtual 38 | self.loc = { 39 | 'll': 0, 40 | 'ur': 0, 41 | 'ul': 0, 42 | 'lr': 0, 43 | 'l': 0, 44 | 'r': 0, 45 | 't': 0, 46 | 'b': 0, 47 | 'cl': 0, 48 | 'cr': 0, 49 | 'ct': 0, 50 | 'cb': 0, 51 | 'c': 0 52 | } 53 | self.edges = ['l', 'r', 'b', 't'] 54 | self.v_edges = ['t', 'b'] 55 | self.h_edges = ['l', 'r'] 56 | 57 | # Init rect locations 58 | self.update_dict() 59 | 60 | # Describes all the required keys to define a Rect2 object with a dict 61 | dict_compatability = ('handle0', 'handle1', 'xy0', 'xy1', 'layer') 62 | 63 | @classmethod 64 | def from_dict(cls, params: dict) -> 'Rectangle': 65 | """ Enable the creation of a rectangle from a dictionary of parameters """ 66 | # Check that all parameters required to create keys exist 67 | if not all(keys in params.keys() for keys in cls.dict_compatability): 68 | raise ValueError('Provided dict does not contain all parameters required to specify Rect2 obj') 69 | else: 70 | handles = [params['handle0'], params['handle1']] 71 | coordinates = [params['xy0'], params['xy1']] 72 | 73 | # Calculate ll and ur coordinates based on provided handle locations 74 | if ('ll' in handles) and ('ur' in handles): 75 | ll_loc = coordinates[handles.index('ll')] 76 | ur_loc = coordinates[handles.index('ur')] 77 | xy = [ll_loc, ur_loc] 78 | elif ('ul' in handles) and ('lr' in handles): 79 | ul_loc = coordinates[handles.index('ul')] 80 | lr_loc = coordinates[handles.index('lr')] 81 | ll_loc = [ul_loc[0], lr_loc[1]] 82 | ur_loc = [lr_loc[0], ul_loc[1]] 83 | xy = [ll_loc, ur_loc] 84 | else: 85 | raise ValueError('Provided handles do not adequately constrain rectangle dimensions') 86 | 87 | if 'virtual' in params.keys(): 88 | virtual = params['virtual'] 89 | else: 90 | virtual = False 91 | 92 | # construct class based on cleaned up xy coordinates 93 | rect = cls(xy, params['layer'], virtual) 94 | return rect 95 | 96 | """ Properties """ 97 | 98 | @property 99 | def layer(self): 100 | return self._lpp[0] 101 | # return self._lpp 102 | 103 | @layer.setter 104 | def layer(self, value): 105 | if isinstance(value, tuple) and len(value) == 2: 106 | self._lpp = value 107 | elif isinstance(value, list) and len(value) == 2: 108 | self._lpp = tuple(value) 109 | elif isinstance(value, str): 110 | self._lpp = (value, 'drawing') 111 | else: 112 | raise ValueError(f"{value} cannot be used as a layer or layer purpose pair") 113 | 114 | @property 115 | def lpp(self) -> Tuple[str, str]: 116 | return self._lpp 117 | 118 | @lpp.setter 119 | def lpp(self, value): 120 | if len(value == 2): 121 | self._lpp = tuple(value) 122 | else: 123 | raise ValueError(f"{value} cannot be used as a layer purpose pair") 124 | 125 | @property 126 | def ll(self) -> XY: 127 | return self._ll 128 | 129 | @ll.setter 130 | def ll(self, xy): 131 | self._ll = XY(xy) 132 | 133 | @property 134 | def ur(self) -> XY: 135 | return self._ur 136 | 137 | @ur.setter 138 | def ur(self, xy): 139 | self._ur = XY(xy) 140 | 141 | @property 142 | def xy(self): 143 | return [self.ll, self.ur] 144 | 145 | @xy.setter 146 | def xy(self, coordinates): 147 | self.ll = coordinates[0] 148 | self.ur = coordinates[1] 149 | 150 | @property 151 | def width(self) -> float: 152 | return self.get_dim('x') 153 | 154 | @property 155 | def height(self) -> float: 156 | return self.get_dim('y') 157 | 158 | @property 159 | def center(self) -> XY: 160 | return self.loc['c'] 161 | 162 | """ Magic Methods """ 163 | 164 | def __repr__(self): 165 | return 'Rectangle(xy={!s}, layer={}, virtual={})'.format(self.xy, self.layer, self.virtual) 166 | 167 | def __str__(self): 168 | return '\tloc: {} \n\tlayer: {} \n\tvirtual: {}'.format(self.xy, self.layer, self.virtual) 169 | 170 | """ Utility Methods """ 171 | 172 | def export_locations(self): 173 | return self.loc 174 | 175 | def update_dict(self): 176 | """ Updates the location dictionary based on the current ll and ur coordinates """ 177 | self.loc = { 178 | 'll': self.ll, 179 | 'ur': self.ur, 180 | 'ul': XY([self.ll.x, self.ur.y]), 181 | 'lr': XY([self.ur.x, self.ll.y]), 182 | 'l': self.ll.x, 183 | 'r': self.ur.x, 184 | 't': self.ur.y, 185 | 'b': self.ll.y, 186 | 'cl': XY([self.ll.x, .5 * (self.ur.y + self.ll.y)]), 187 | 'cr': XY([self.ur.x, .5 * (self.ur.y + self.ll.y)]), 188 | 'ct': XY([.5 * (self.ll.x + self.ur.x), self.ur.y]), 189 | 'cb': XY([.5 * (self.ll.x + self.ur.x), self.ll.y]), 190 | 'c': XY([.5 * (self.ll.x + self.ur.x), .5 * (self.ur.y + self.ll.y)]) 191 | } 192 | 193 | def set_dim(self, dim: str, size: float) -> 'Rectangle': 194 | """ Sets either the width or height of the rect to desired value. Maintains center location of rect """ 195 | if dim == 'x': 196 | self.ll.x = self.loc['cb'].x - (.5 * size) 197 | self.ur.x = self.loc['ct'].x + (.5 * size) 198 | elif dim == 'y': 199 | self.ll.y = self.loc['cl'].y - (.5 * size) 200 | self.ur.y = self.loc['cr'].y + (.5 * size) 201 | elif dim == 'xy': 202 | self.ll.x = self.loc['cb'].x - (.5 * size) 203 | self.ur.x = self.loc['ct'].x + (.5 * size) 204 | self.ll.y = self.loc['cl'].y - (.5 * size) 205 | self.ur.y = self.loc['cr'].y + (.5 * size) 206 | else: 207 | raise ValueError('target_dim must be either x or y') 208 | # Update rectangle locations 209 | self.update_dict() 210 | return self 211 | 212 | def get_dim(self, dim): 213 | """ Returns measurement of the dimension of the rectangle """ 214 | if dim == 'x': 215 | return abs(self.loc['l'] - self.loc['r']) 216 | elif dim == 'y': 217 | return abs(self.loc['t'] - self.loc['b']) 218 | else: 219 | raise ValueError('Provided dimension is not valid') 220 | 221 | def scale(self, size, dim=None) -> 'Rectangle': 222 | """ Additvely resizes the rectangle by the provided size """ 223 | if dim == 'x': 224 | self.set_dim('x', self.get_dim('x') + size) 225 | elif dim == 'y': 226 | self.set_dim('y', self.get_dim('y') + size) 227 | else: 228 | self.set_dim('x', self.get_dim('x') + size) 229 | self.set_dim('y', self.get_dim('y') + size) 230 | return self 231 | 232 | def align(self, 233 | target_handle: str, 234 | ref_rect: 'Rectangle' = None, 235 | ref_handle: str = None, 236 | track=None, 237 | align_opt: Tuple[bool, bool] = (True, True), 238 | offset: coord_type = (0, 0) 239 | ) -> 'Rectangle': 240 | """ Moves the rectangle to co-locate the target and ref handles """ 241 | if track is not None: 242 | # If a track is provided, calculate difference in dimension of track 243 | if track.x != 0: 244 | diffx = self.loc[target_handle].x - (track.x - offset[0]) 245 | else: 246 | diffx = 0 247 | if track.y != 0: 248 | diffy = self.loc[target_handle].y - (track.y - offset[1]) 249 | else: 250 | diffy = 0 251 | 252 | elif (ref_rect is None) and (ref_handle is None): 253 | # If no reference rectangle/handle is given 254 | if target_handle in self.loc: 255 | diffx = self.loc[target_handle].x - offset[0] 256 | diffy = self.loc[target_handle].y - offset[1] 257 | 258 | elif (target_handle in self.loc) and (ref_handle in ref_rect.loc): 259 | # If a reference rectangle and handles are given 260 | if (target_handle in self.edges) and (ref_handle in self.edges): 261 | # If we're provided with edges instead of vertices 262 | if (target_handle in self.v_edges) and (ref_handle in self.v_edges): 263 | # If both handles are vertical edges 264 | diffx = 0 265 | diffy = self.loc[target_handle] - (ref_rect.loc[ref_handle] + offset[1]) 266 | elif (target_handle in self.h_edges) and (ref_handle in self.h_edges): 267 | # If both handles are horizontal edges 268 | diffx = self.loc[target_handle] - (ref_rect.loc[ref_handle] + offset[0]) 269 | diffy = 0 270 | else: 271 | raise ValueError('{} and {} must both be edge handles to support edge alignment'. 272 | format(target_handle, ref_handle)) 273 | else: 274 | # Compute difference in location 275 | diffx = self.loc[target_handle].x - (ref_rect.loc[ref_handle].x + offset[0]) 276 | diffy = self.loc[target_handle].y - (ref_rect.loc[ref_handle].y + offset[1]) 277 | else: 278 | raise ValueError('Arguments do not specify a valid align operation') 279 | 280 | # Shift ll and ur locations to co-locate handles, unless align_opt is false 281 | if align_opt[0] or (align_opt is None): 282 | self.ll.x -= diffx 283 | self.ur.x -= diffx 284 | if align_opt[1] or (align_opt is None): 285 | self.ll.y -= diffy 286 | self.ur.y -= diffy 287 | # Update rectangle locations 288 | self.update_dict() 289 | return self 290 | 291 | def stretch(self, 292 | target_handle: str, 293 | ref_rect=None, 294 | ref_handle: str = None, 295 | track=None, 296 | stretch_opt=(True, True), # type: Tuple[bool, bool] 297 | offset=(0, 0) # type: Tuple[float, float] 298 | ) -> 'Rectangle': 299 | """ 300 | Stretches rectangle to co-locate the target and ref handles. If ref handles are not provided, 301 | stretch by given offset 302 | """ 303 | 304 | if track is not None: 305 | # If a track is provided, calculate difference only in dimension of track 306 | if track.x != 0: 307 | diff_width = self.loc[target_handle].x - (track.x - offset[0]) 308 | else: 309 | diff_width = 0 310 | if track.y != 0: 311 | diff_height = self.loc[target_handle].y - (track.y - offset[1]) 312 | else: 313 | diff_height = 0 314 | 315 | elif (ref_rect is None) and (ref_handle is None): 316 | # If no reference rect/handle is provided, stretch the target rect by provided offset 317 | if target_handle in self.loc: 318 | # Check that the handle is valid 319 | diff_width = self.loc[target_handle].x - offset[0] 320 | diff_height = self.loc[target_handle].y - offset[1] 321 | # First align the two points 322 | self.align(target_handle, align_opt=stretch_opt, offset=offset) 323 | else: 324 | raise ValueError('target handle is invalid') 325 | 326 | elif (target_handle in self.loc) and (ref_handle in ref_rect.loc): 327 | # If a reference rect and handles are given 328 | if (target_handle in self.edges) and (ref_handle in self.edges): 329 | # If we're provided with edges instead of vertices 330 | if (target_handle in self.v_edges) and (ref_handle in self.v_edges): 331 | # If both handles are vertical edges 332 | diff_width = 0 333 | diff_height = self.loc[target_handle] - (ref_rect.loc[ref_handle] + offset[1]) 334 | elif (target_handle in self.h_edges) and (ref_handle in self.h_edges): 335 | # If both handles are horizontal edges 336 | diff_width = self.loc[target_handle] - (ref_rect.loc[ref_handle] + offset[0]) 337 | diff_height = 0 338 | else: 339 | raise ValueError('{} and {} must both be edge handles of same dimension to support edge alignment'. 340 | format(target_handle, ref_handle)) 341 | self.align(target_handle, ref_rect, ref_handle, align_opt=stretch_opt, offset=offset) 342 | else: 343 | diff_width = self.loc[target_handle].x - (ref_rect.loc[ref_handle].x + offset[0]) 344 | diff_height = self.loc[target_handle].y - (ref_rect.loc[ref_handle].y + offset[1]) 345 | # First align the two points 346 | self.align(target_handle, ref_rect, ref_handle, align_opt=stretch_opt, offset=offset) 347 | else: 348 | raise ValueError('Arguments do not specify a valid stretch operation') 349 | 350 | # Shift ll and ur locations to co-locate handles, unless stretch_opt is false 351 | # Stretch width of rectangle 352 | if target_handle == 'll' or target_handle == 'ul' or target_handle == 'cl' or target_handle == 'l': 353 | if stretch_opt[0]: 354 | self.ur.x += diff_width 355 | elif target_handle == 'lr' or target_handle == 'ur' or target_handle == 'cr' or target_handle == 'r': 356 | if stretch_opt[0]: 357 | self.ll.x += diff_width 358 | elif target_handle == 'c' or target_handle == 'ct' or target_handle == 'cb': 359 | if stretch_opt[0]: 360 | self.ll.x += .5 * diff_width 361 | self.ur.x += .5 * diff_width 362 | 363 | # Stretch height of rectangle 364 | if target_handle == 'll' or target_handle == 'lr' or target_handle == 'cb' or target_handle == 'b': 365 | if stretch_opt[1]: 366 | self.ur.y += diff_height 367 | elif target_handle == 'ul' or target_handle == 'ur' or target_handle == 'ct' or target_handle == 't': 368 | if stretch_opt[1]: 369 | self.ll.y += diff_height 370 | elif target_handle == 'c' or target_handle == 'cl' or target_handle == 'cr': 371 | if stretch_opt[1]: 372 | self.ll.y += .5 * diff_height 373 | self.ur.y += .5 * diff_height 374 | 375 | # Update rectangle locations 376 | self.update_dict() 377 | return self 378 | 379 | def shift_origin(self, origin=(0, 0), orient='R0', virtual=True) -> 'Rectangle': 380 | """ 381 | Takes xy coordinates and rotation, returns a virtual Rect2 that is re-referenced to the new origin 382 | Assumes that the origin of the rectangle is (0, 0) 383 | """ 384 | # Apply the transformation to the xy coordinates that describe the rectangle 385 | new_xy = [0, 0] 386 | new_xy[0] = self.ll.shift_origin(origin, orient) 387 | new_xy[1] = self.ur.shift_origin(origin, orient) 388 | 389 | # Depending on the transformation, ll and ur may describe different rect corner 390 | if orient is 'MX': 391 | handle0 = 'ul' 392 | handle1 = 'lr' 393 | elif orient is 'MY': 394 | handle0 = 'lr' 395 | handle1 = 'ul' 396 | elif orient is 'R180': 397 | handle0 = 'ur' 398 | handle1 = 'll' 399 | else: 400 | handle0 = 'll' 401 | handle1 = 'ur' 402 | 403 | # Return the new shifted rectangle created from dictionary 404 | rect_dict = { 405 | 'handle0': handle0, 406 | 'handle1': handle1, 407 | 'xy0': new_xy[0], 408 | 'xy1': new_xy[1], 409 | 'virtual': virtual, 410 | 'layer': self.lpp 411 | } 412 | return Rectangle.from_dict(rect_dict) 413 | 414 | def to_bbox(self) -> BBox: 415 | return BBox(self.ll.x, self.ll.y, self.ur.x, self.ur.y, self._res) 416 | 417 | def copy(self, virtual=False, layer=None) -> 'Rectangle': 418 | if layer is None: 419 | layer = self.lpp 420 | return Rectangle(self.xy, layer, virtual=virtual) 421 | 422 | def get_overlap(self, 423 | rect: 'Rectangle', 424 | virtual: bool = True 425 | ) -> 'Rectangle': 426 | """ Returns a rectangle corresponding to the overlapped region between two rectangles """ 427 | 428 | # Determine bounds of the intersection in x dimension 429 | x_min = max(self.ll.x, rect.ll.x) 430 | x_max = min(self.ur.x, rect.ur.x) 431 | 432 | # Determine bounds of the intersection in y dimension 433 | y_min = max(self.ll.y, rect.ll.y) 434 | y_max = min(self.ur.y, rect.ur.y) 435 | 436 | # Throw exception if the rectangles don't overlap 437 | if x_min > x_max: 438 | print(self) 439 | print(rect) 440 | print('x_min: {}'.format(x_min)) 441 | print('x_max: {}'.format(x_max)) 442 | raise ValueError('Given rectangles do not overlap in x direction') 443 | if y_min > y_max: 444 | print(self) 445 | print(rect) 446 | print('y_min: {}'.format(y_min)) 447 | print('y_max: {}'.format(y_max)) 448 | raise ValueError('Given rectangles do not overlap in y direction') 449 | 450 | return Rectangle([[x_min, y_min], 451 | [x_max, y_max]], 452 | layer=self.lpp, 453 | virtual=virtual) 454 | 455 | def get_enclosure(self, 456 | rect: 'Rectangle', 457 | virtual: bool = True 458 | ) -> 'Rectangle': 459 | """ Returns a rectangle that encloses all provided rectangles """ 460 | ll = [min(self.ll.x, rect.ll.x), min(self.ll.y, rect.ll.y)] 461 | ur = [max(self.ur.x, rect.ur.x), max(self.ur.y, rect.ur.y)] 462 | return Rectangle(xy=[ll, ur], layer=self.get_highest_layer(rect), virtual=virtual) 463 | 464 | def get_highest_layer(self, 465 | rect: Optional['Rectangle'] = None, 466 | layer: Optional[str] = None 467 | ) -> Tuple[str, str]: 468 | """ Returns the highest layer used by provided rectangles """ 469 | layerstack = tech_info.tech_info['metal_tech']['layerstack'] # TODO: Access layerstack from bag tech 470 | if rect: 471 | layer = rect.layer 472 | lpp = rect.lpp 473 | else: 474 | layer = layer 475 | lpp = (layer, 'drawing') 476 | 477 | # Check for non-routing layers and return the highest routing layer 478 | # TODO: Clean up this logic to deal with non-routing layers 479 | if (self.layer not in layerstack) and (layer not in layerstack): 480 | # print(f'both {self.layer} and {layer} are not valid routing layers, and cannot be ordered') 481 | return self.lpp 482 | elif self.layer not in layerstack: 483 | return lpp 484 | elif layer not in layerstack: 485 | return self.lpp 486 | 487 | i1 = layerstack.index(self.layer) 488 | i2 = layerstack.index(layer) 489 | if i2 > i1: 490 | return lpp 491 | else: 492 | return self.lpp 493 | 494 | def get_midpoint(self, 495 | handle: str, 496 | coord: coord_type 497 | ) -> coord_type: 498 | """ 499 | Gets the midpoint between a location on this rectangle and another coordinate 500 | """ 501 | my_loc = self.loc[handle] 502 | their_loc = XY(coord) 503 | mid_x = (my_loc.x + their_loc.x) / 2 504 | mid_y = (my_loc.y + their_loc.y) / 2 505 | return XY([mid_x, mid_y]) 506 | 507 | @staticmethod 508 | def overlap(A, B): 509 | """ 510 | Returns whether or not two rectangles overlap in both dimensions 511 | """ 512 | x_min = max(A.ll.x, B.ll.x) 513 | x_max = min(A.ur.x, B.ur.x) 514 | y_min = max(A.ll.y, B.ll.y) 515 | y_max = min(A.ur.y, B.ur.y) 516 | 517 | return x_min < x_max and y_min < y_max 518 | -------------------------------------------------------------------------------- /ACG/AyarLayoutGenerator.py: -------------------------------------------------------------------------------- 1 | """ 2 | The AyarLayoutGenerator module implements classes to generate full-custom layout without a grid. It allows designers 3 | to describe layout generation scripts in the Python language and automate the layout process. 4 | """ 5 | 6 | # General imports 7 | import abc 8 | from typing import Union, Tuple, List, Optional 9 | import re 10 | import yaml 11 | import os 12 | 13 | # BAG imports 14 | import bag 15 | from bag.layout.template import TemplateBase 16 | 17 | # ACG imports 18 | from ACG.Rectangle import Rectangle 19 | from ACG.Track import Track, TrackManager 20 | from ACG.VirtualInst import VirtualInst 21 | from ACG.Via import ViaStack, Via 22 | from ACG import tech as tech_info 23 | from ACG.LayoutParse import CadenceLayoutParser 24 | 25 | 26 | class AyarLayoutGenerator(TemplateBase, metaclass=abc.ABCMeta): 27 | """ 28 | The AyarLayoutGenerator class implements functions and variables for full-custom layout generations on physical 29 | grids 30 | """ 31 | 32 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 33 | # Call TemplateBase's constructor 34 | TemplateBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 35 | self.tech = self.grid.tech_info 36 | self._res = .001 # set basic grid size to be 1nm 37 | # Create a dictionary that holds all objects required to construct the layout 38 | self._db = { 39 | 'rect': [], 40 | 'via': [], 41 | 'prim_via': [], 42 | 'instance': [], 43 | 'template': [] 44 | } 45 | 46 | # Create an empty database that will store only the relevant layout objects 47 | self.loc = {} 48 | 49 | # Manage the tracks in a track manager 50 | self.tracks = TrackManager.from_routing_grid(self.grid) 51 | 52 | # Pull default layout parameters 53 | self.params = self.__class__.get_default_param_values() 54 | # Override defaults and add provided parameters 55 | for key in params: 56 | self.params[key] = params[key] 57 | 58 | # Set up properties for BAG black-boxing 59 | self.temp_boundary = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) # TODO: Make process agnostic 60 | self.prim_bound_box = None 61 | self.prim_top_layer = None 62 | 63 | """ REQUIRED METHODS """ 64 | """ You must implement these methods for BAG to work """ 65 | 66 | @classmethod 67 | @abc.abstractmethod 68 | def get_params_info(cls) -> dict: 69 | """ Return a dictionary describing all parameters in the layout generator """ 70 | return dict() 71 | 72 | @classmethod 73 | def get_default_param_values(cls) -> dict: 74 | """ Return a dictionary of all default parameter values """ 75 | return dict() 76 | 77 | def export_locations(self) -> dict: 78 | """ 79 | Returns a dictionary of shapes/inst in the layout. It is recommended to override this method and only return 80 | relevant shapes in a dict() with easily interpretable key names 81 | """ 82 | return self.loc 83 | 84 | @abc.abstractmethod 85 | def layout_procedure(self): 86 | """ Implement this method to describe how the layout is drawn """ 87 | pass 88 | 89 | """ DRAWING TOOLS """ 90 | """ Call these methods to craft your layout """ 91 | """ DO NOT OVERRIDE """ 92 | 93 | def add_rect(self, 94 | layer: Optional[Union[str, Tuple[str, str], List[str]]] = None, 95 | xy=None, 96 | virtual: bool = False, 97 | *, 98 | index: Optional[int] = None 99 | ) -> Rectangle: 100 | """ 101 | Instantiates a rectangle, adds the Rectangle object to local db, and returns it for further user manipulation 102 | 103 | Parameters 104 | ---------- 105 | layer : Optional[str] 106 | layer that the rectangle should be drawn on 107 | xy : Tuple[[float, float], [float, float]] 108 | list of xy coordinates representing the lower left and upper right corner of the rectangle. If None, 109 | select default size of 100nm by 100nm at origin 110 | virtual : bool 111 | If true, the rectangle object will be created but will not be drawn in the final layout. If false, the 112 | rectangle will be drawn as normal in the final layout 113 | index : Optional[int] 114 | If provided, will look up the layer name associated with the index, and then draw the rectangle on 115 | that layer. 116 | 117 | Returns 118 | ------- 119 | rect: Rectangle 120 | the created rectangle object 121 | """ 122 | # If a layer index is provided, reverse-lookup the layer name 123 | if index is not None: 124 | layer = self.grid.tech_info.get_layer_name(index) 125 | 126 | # Only try default sizing if the user does not provide xy coordinate 127 | if xy is None: 128 | layer_params = tech_info.tech_info['metal_tech']['metals'] 129 | 130 | # This variable holds the name of the metal to be looked up in the tech params 131 | layer_lookup = layer 132 | if isinstance(layer, list): 133 | layer_lookup = layer[0] 134 | 135 | # If we can find our layer in the metal tech params, use its default width 136 | if layer_lookup in layer_params: 137 | metal_params = tech_info.tech_info['metal_tech']['metals'][layer_lookup] 138 | default_w = metal_params['min_width'] 139 | # Otherwise just default to 100nm width 140 | else: 141 | default_w = 0.1 142 | xy = [[0, 0], [default_w, default_w]] 143 | 144 | # Otherwise just directly create the rectangle according to the users specification 145 | self._db['rect'].append(Rectangle(xy, layer=layer, virtual=virtual)) 146 | return self._db['rect'][-1] 147 | 148 | def copy_rect(self, 149 | rect: Rectangle, 150 | layer=None, # type: Union[str, Tuple[str, str]] 151 | virtual: bool = False 152 | ) -> Rectangle: 153 | """ 154 | Creates a copy of the given rectangle and adds it to the local db 155 | 156 | Args: 157 | rect (Rectangle): 158 | rectangle object to be copied 159 | layer (str): 160 | layer that the copied rectangle should be drawn on. If None, the copied rectangle will use the same 161 | layer as the provided rectangle 162 | virtual (bool): 163 | If true, the rectangle object will be created but will not be drawn in the final layout. If false, the 164 | rectangle will be drawn as normal in the final layout 165 | 166 | Returns: 167 | (Rectangle): 168 | a new rectangle object copied from provided rectangle 169 | """ 170 | temp = rect.copy(layer=layer, virtual=virtual) 171 | self._db['rect'].append(temp) 172 | return self._db['rect'][-1] 173 | 174 | def add_track(self, name: str, dim: str, spacing: float, origin: float = 0) -> Track: 175 | """ 176 | Creates and returns a track object for alignment use 177 | 178 | Parameters 179 | ---------- 180 | name 181 | Name to use for the added track 182 | dim: 183 | 'x' for a horizontal track and 'y' for a vertical track 184 | spacing: 185 | number representing the space between tracks 186 | origin: 187 | coordinate for the 0th track 188 | 189 | Returns 190 | ------- 191 | Track: 192 | track object for user manipulation 193 | """ 194 | self.tracks.add_track(name=name, dim=dim, spacing=spacing, origin=origin) 195 | return self.tracks[name] 196 | 197 | def new_template(self, 198 | params: dict = None, 199 | temp_cls=None, 200 | debug: bool = False, 201 | **kwargs): 202 | """ 203 | Generates a layout master of specified class and parameter set 204 | 205 | Args: 206 | params (dict): 207 | dictionary of parameters to specify the layout to be created 208 | temp_cls: 209 | the layout generator class to be used 210 | debug (bool): 211 | True to print debug messages 212 | """ 213 | return TemplateBase.new_template(self, 214 | params=params, 215 | temp_cls=temp_cls, 216 | debug=debug, **kwargs) 217 | 218 | def add_instance(self, 219 | master, 220 | inst_name=None, 221 | loc=(0, 0), 222 | orient="R0", 223 | ) -> VirtualInst: 224 | """ Adds a single instance from a provided template master """ 225 | temp = VirtualInst(master, inst_name=inst_name) 226 | temp.shift_origin(loc, orient=orient) # Move virtual instance to desired location/orientation 227 | self._db['instance'].append(temp) # Add the instance to the list 228 | return temp 229 | 230 | def import_layout(self, 231 | libname: str, 232 | cellname: str, 233 | yaml_root: str = None, 234 | export_pins: bool = True 235 | ) -> 'LayoutAbstract': 236 | """ 237 | Creates an abstract layout master from the provided virtuoso libname and cellname. Adds pin shapes 238 | from the yaml root to the location dictionary for easy access. 239 | 240 | Parameters 241 | ---------- 242 | libname : str 243 | virtuoso design library name 244 | cellname : str 245 | virtuoso cell name; must be contained within the provided virtuoso design library 246 | yaml_root : str 247 | path to yaml file containing pin shapes. These shapes will be added to the location dictionary 248 | export_pins : bool 249 | if True, will draw all pin shapes provided in yaml root 250 | 251 | Returns 252 | ------- 253 | abstract_temp : LayoutAbstract 254 | A design master containing the generated layout_abstract 255 | """ 256 | params = { 257 | 'libname': libname, 258 | 'cellname': cellname, 259 | 'yaml_root': yaml_root, 260 | 'export_pins': export_pins 261 | } 262 | abstract_temp = self.new_template(params=params, temp_cls=LayoutAbstract) 263 | return abstract_temp 264 | 265 | def import_cadence_layout(self, 266 | libname: str, 267 | cellname: str 268 | ) -> 'AyarLayoutGenerator': 269 | """ 270 | This method will extract the layout specified by libname, cellname from Cadence, and return a new 271 | LayoutAbstract master that can be placed in the current db. This method will also analyze the layout for 272 | its pins and automatically add them to the location dictionary. 273 | 274 | Parameters 275 | ---------- 276 | libname : str 277 | Cadence library name where the cell will be located 278 | cellname : str 279 | Cadence cell name of the layout to be imported 280 | 281 | Returns 282 | ------- 283 | master : LayoutAbstract 284 | Newly created master that will contain the imported layout and pin information 285 | """ 286 | # Grab layout information from Cadence through SKILL interface 287 | temp_file_name = 'test.yaml' 288 | expr = 'parse_cad_layout( "%s" "%s" "%s" )' % (libname, cellname, temp_file_name) 289 | self.template_db._prj.impl_db._eval_skill(expr) 290 | 291 | # Grab the raw layout data, then delete the temp file afterward 292 | with open(temp_file_name, 'r') as f: 293 | data = yaml.load(f) 294 | os.remove(temp_file_name) 295 | 296 | # Create the new master 297 | return self.new_template(params={'libname': libname, 298 | 'cellname': cellname, 299 | 'data': data}, 300 | temp_cls=CadenceLayout) 301 | 302 | def connect_wires(self, 303 | rect1: Rectangle, 304 | rect2: Rectangle, 305 | size: Tuple[int, int] = (None, None), 306 | extend: bool = False, 307 | prim: bool = False 308 | ) -> Union[ViaStack, Via]: 309 | """ 310 | Creates a via stack between the two provided rectangles. This method requires that the provided rectangles 311 | overlap. No knowledge of which rectangle is higher/lower in the metal stack is needed. 312 | 313 | Parameters 314 | ---------- 315 | rect1: Rectangle 316 | first rectangle to be connected 317 | rect2: Rectangle 318 | second rectangle to be connected 319 | size: Tuple[int, int] 320 | number of vias to be placed in the x and y dimension respectively. If (None, None), vias will be placed to 321 | fill the enclosure 322 | extend: bool 323 | Represents whether the enclosure will be extended in the x or y direction to meet drc rules 324 | prim: bool 325 | if True, will attempt to create a single primitive via instead of a via stack 326 | """ 327 | # Only create a via if the two rectangles are actually on different layers 328 | # TODO: Add support for returning empty vias when the two layers are the same 329 | if rect1.layer != rect2.layer: 330 | if prim: 331 | temp = Via.from_metals(rect1, rect2, size=size) 332 | self._db['prim_via'].append(temp) 333 | else: 334 | temp = ViaStack(rect1, rect2, size=size, extend=extend) 335 | self._db['via'].append(temp) 336 | return temp 337 | 338 | def add_prim_via(self, 339 | via_id: str, 340 | rect: Rectangle, 341 | size: Tuple[int, int] = (None, None), 342 | ) -> Via: 343 | """ 344 | Creates a via stack between the two provided rectangles. This method requires that the provided rectangles 345 | overlap. No knowledge of which rectangle is higher/lower in the metal stack is needed. 346 | 347 | Parameters 348 | ---------- 349 | via_id: str 350 | id for the via to be drawn 351 | rect: Rectangle 352 | the rectangle bounding the region to draw the via 353 | size: Tuple[int, int] 354 | number of vias to be placed in the x and y dimension respectively. If (None, None), vias will be placed to 355 | fill the enclosure 356 | """ 357 | temp = Via(via_id, bbox=rect, size=size) 358 | self._db['prim_via'].append(temp) 359 | return temp 360 | 361 | def create_label(self, label, rect, purpose=None, show=True): 362 | if purpose is not None: 363 | self.add_rect([rect.layer, purpose], rect.xy) 364 | if show is True: 365 | TemplateBase.add_label(self, label, rect.layer, rect.to_bbox()) 366 | TemplateBase.add_pin_primitive(self, net_name=label, layer=rect.layer, bbox=rect.to_bbox(), show=False) 367 | 368 | """ INTERNAL METHODS """ 369 | """ DO NOT CALL OR OVERRIDE """ 370 | 371 | def draw_layout(self) -> None: 372 | """ Called by higher level BAG functions to create layout """ 373 | self.layout_procedure() # Perform the user determined layout process 374 | self._commit_shapes() # Take all of the created shapes and actually push them to the bag db 375 | 376 | def parse_yaml(self, pathname) -> dict: 377 | """ returns the parsed yaml file TODO: change the reference function to something not in bag.core """ 378 | return bag.core._parse_yaml_file(pathname) 379 | 380 | def _commit_shapes(self) -> None: 381 | """ Takes all shapes in local db and creates standard BAG equivalents """ 382 | self._commit_rect() 383 | self._commit_inst() 384 | self._commit_via() 385 | 386 | # Set the properties required for BAG primitive black boxing 387 | self.prim_bound_box = self.temp_boundary.to_bbox() 388 | self.prim_top_layer = self.grid.tech_info.get_layer_id(self.temp_boundary.layer) 389 | 390 | # for layer_num in range(1, self.prim_top_layer + 1): 391 | # self.mark_bbox_used(layer_num, self.prim_bound_box) 392 | 393 | def _commit_rect(self) -> None: 394 | """ Takes in all rectangles in the db and creates standard BAG equivalents """ 395 | for shape in self._db['rect']: 396 | self.temp_boundary = self.temp_boundary.get_enclosure(shape) 397 | if shape.virtual is False: 398 | TemplateBase.add_rect(self, shape.lpp, shape.to_bbox()) 399 | 400 | def _commit_inst(self) -> None: 401 | """ Takes in all inst in the db and creates standard BAG equivalents """ 402 | for inst in self._db['instance']: 403 | # Get the boundary of the instance 404 | try: 405 | bound = inst.master.temp_boundary.shift_origin(origin=inst.origin, orient=inst.orient) 406 | except AttributeError: 407 | # TODO: Get the size properly 408 | bound = Rectangle(xy=[[0, 0], [.1, .1]], layer='M1', virtual=True) 409 | self.temp_boundary = self.temp_boundary.get_enclosure(bound) 410 | TemplateBase.add_instance(self, 411 | inst.master, 412 | inst_name=inst.inst_name, 413 | loc=inst.origin, 414 | orient=inst.orient) 415 | 416 | def _commit_via(self) -> None: 417 | """ Takes in all vias in the db and creates standard BAG equivalents """ 418 | for via in self._db['via']: 419 | for connection in via.metal_pairs: 420 | TemplateBase.add_via(self, 421 | bbox=via.loc['overlap'].to_bbox(), 422 | bot_layer=connection[0], 423 | top_layer=connection[1], 424 | bot_dir=connection[2], 425 | extend=via.extend) 426 | for via in self._db['prim_via']: 427 | TemplateBase.add_via_primitive(self, 428 | via_type=via.via_id, 429 | loc=via.location, 430 | num_rows=via.num_rows, 431 | num_cols=via.num_cols, 432 | sp_rows=via.sp_rows, 433 | sp_cols=via.sp_cols, 434 | enc1=via.enc_bot, 435 | enc2=via.enc_top, 436 | orient=via.orient) 437 | 438 | 439 | class LayoutAbstract(AyarLayoutGenerator): 440 | """ 441 | Generator class that instantiates an existing layout with LEF equivalent pins/obs(optional) 442 | """ 443 | 444 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 445 | # Call super class' constructor 446 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 447 | 448 | self.loc = {} 449 | self.cell_dict = None 450 | self.yaml_root = self.params['yaml_root'] 451 | self.cell_yaml = self.yaml_root + self.params['libname'] + '/' + self.params['cellname'] + '.yaml' 452 | self.tech_layers = [] 453 | self.pin_list = [] 454 | 455 | @classmethod 456 | def get_params_info(cls): 457 | return dict( 458 | libname='Name of the library to instantiate the cell from', 459 | cellname='Name of the cell to instantiate', 460 | yaml_root='Root directory path for yaml files' 461 | ) 462 | 463 | def get_pins(self): 464 | return self.pin_list 465 | 466 | def layout_procedure(self): 467 | """Draws the layout and caculates the coordinates based on LEF""" 468 | self.get_tech_params() 469 | self.get_cell_params() 470 | self.calculate_pins() 471 | # self.calculate_obs() # Can be enabled later if needed!!! 472 | self.calculate_boundary() 473 | self.instantiate_layout() 474 | 475 | def instantiate_layout(self): 476 | """We instantiate the layout as a primitive here based on the cell_name read""" 477 | """Adding mapping since the stupid lef layout library name is different than the cds.lib name""" 478 | 479 | self.add_instance_primitive(lib_name=self.params['libname'], cell_name=self.params['cellname'], loc=(0, 0)) 480 | 481 | def get_cell_params(self): 482 | """Read cell parameters from specific Yaml""" 483 | pathname = open(self.cell_yaml, mode='r') 484 | self.cell_dict = yaml.load(pathname) 485 | print("{} instantiated".format(self.params['cellname'])) 486 | 487 | def get_tech_params(self): 488 | """Get tech information to ensure that information of metal stacks is passed through yaml and not hardcoded""" 489 | self.tech_layers = tech_info.tech_info['metal_tech']['routing'] 490 | 491 | def calculate_pins(self): 492 | """Calculates the pins on the stdcell/macro and pushes them to loc dict""" 493 | for keys in self.cell_dict: 494 | if re.match('pins', keys): 495 | for pins in self.cell_dict['pins']: 496 | pins_dict = {pins: []} 497 | for layers in self.cell_dict['pins'][pins]: 498 | if layers.upper() in self.tech_layers: 499 | for rects in self.cell_dict['pins'][pins][layers]: 500 | shape = self.add_rect(layers.upper(), rects, virtual=True) 501 | pins_dict[pins].append(shape) 502 | self.loc.update(pins_dict) 503 | self.pin_list.append(pins) 504 | 505 | # self.copy_rect(shape,layer=(layers.upper(), 'label')) 506 | # self.create_label(pins,shape) 507 | 508 | def calculate_boundary(self): 509 | """Calulates the boundary from lef file""" 510 | for keys in self.cell_dict: 511 | if re.match('size', keys): 512 | bnd = self.add_rect('OUTLINE', self.cell_dict['size'], virtual=True) 513 | bnd_dict = {'bnd': bnd} 514 | self.loc.update(bnd_dict) 515 | 516 | def calculate_obs(self): 517 | """Calulates obstructions in the lef (Can be pushed to ACG/BAG ), cant see a reason for it yet""" 518 | for keys in self.cell_dict: 519 | if re.match('obs', keys): 520 | for layers in self.cell_dict['obs']: 521 | pass 522 | 523 | 524 | class CadenceLayout(AyarLayoutGenerator): 525 | """ 526 | Generator class that instantiates an existing layout from Cadence and fills the location dict 527 | """ 528 | 529 | def __init__(self, temp_db, lib_name, params, used_names, **kwargs): 530 | AyarLayoutGenerator.__init__(self, temp_db, lib_name, params, used_names, **kwargs) 531 | self.loc = {} 532 | 533 | @classmethod 534 | def get_params_info(cls): 535 | return dict( 536 | libname='Name of the library to instantiate the cell from', 537 | cellname='Name of the cell to instantiate', 538 | data='data for location dict to attach to this master' 539 | ) 540 | 541 | def layout_procedure(self): 542 | parser = CadenceLayoutParser(raw_content=self.params['data']) 543 | self.loc = parser.generate_loc_dict() 544 | self.instantiate_layout() 545 | 546 | def instantiate_layout(self): 547 | """We instantiate the layout as a primitive here based on the cell_name read""" 548 | self.add_instance_primitive(lib_name=self.params['libname'], cell_name=self.params['cellname'], loc=(0, 0)) 549 | -------------------------------------------------------------------------------- /ACG/AutoRouterExtension.py: -------------------------------------------------------------------------------- 1 | from .AyarLayoutGenerator import AyarLayoutGenerator 2 | from .Rectangle import Rectangle 3 | from .XY import XY 4 | from .tech import tech_info 5 | from typing import Tuple, Union, Optional, List, Dict 6 | from .AutoRouter import EZRouter 7 | import copy 8 | 9 | 10 | class EZRouterExtension(EZRouter): 11 | """ 12 | The EZRouterExtension class inherits from the EZRouter class and allows you to create ground-shielded routes. 13 | """ 14 | 15 | # TODO: change tuples to XY to avoid rounding issues. Right now, using XY creates key errors with self.route_point_dict. 16 | 17 | def __init__(self, 18 | gen_cls: AyarLayoutGenerator, 19 | start_rect: Optional[Rectangle] = None, 20 | start_direction: Optional[str] = None, 21 | config: Optional[dict] = None 22 | ): 23 | EZRouter.__init__(self, gen_cls, start_rect=start_rect, start_direction=start_direction, config=config) 24 | 25 | # For AStarRouter use only 26 | self.grids = {} # Dictionary containing grid 2D array for each layer 27 | self.dims = {} # Dictionary of array dimensions for each layer 28 | self.routing_layers = [] # List of layers to route on 29 | 30 | def draw_straight_route_shield(self, 31 | loc: Union[Tuple[float, float], XY], 32 | perpendicular_pitch: float, 33 | parallel_spacing: float, 34 | shield_layers: list, 35 | width: Optional[float] = None 36 | ) -> 'EZRouter': 37 | """ 38 | Routes a straight, shielded metal line from the current location of specified 39 | length. This method does not change the current routing direction. 40 | 41 | Note: This method relies on the fact that stretching a rectangle with an offset 42 | but without a reference rectangle uses the offset as an absolute reference loc 43 | 44 | Parameters 45 | ---------- 46 | loc : Optional[Tuple[float, float]] 47 | XY location to route to. This method will only route in the current direction 48 | perpendicular_pitch : float 49 | The pitch between the perpendicular shielding stripes 50 | parallel_spacing : float 51 | The pitch between the parallel shields 52 | shield_layers : list 53 | A list of layers to route perpendicular shields on 54 | width : Optional[float] 55 | Draws the new route segment with this width if provided 56 | 57 | Returns 58 | ------- 59 | self : EZRouter 60 | Return self to make it easy to cascade connections 61 | """ 62 | if not self.current_rect or not self.current_handle or not self.current_dir: 63 | raise ValueError('Router has not been initialized, please call new_route()') 64 | 65 | # Make a new rectangle and align it to the current route location 66 | new_rect = self.gen.add_rect(layer=self.current_rect.layer) 67 | 68 | # Size the new rectangle to match the current route width 69 | if self.current_dir == '+x' or self.current_dir == '-x': 70 | if width: 71 | new_rect.set_dim('y', width) 72 | else: 73 | new_rect.set_dim('y', self.current_rect.get_dim('y')) 74 | stretch_opt = (True, False) 75 | if self.current_dir == '+x': 76 | new_rect.align('cl', ref_rect=self.current_rect, ref_handle=self.current_handle) 77 | else: 78 | new_rect.align('cr', ref_rect=self.current_rect, ref_handle=self.current_handle) 79 | else: 80 | if width: 81 | new_rect.set_dim('x', width) 82 | else: 83 | new_rect.set_dim('x', self.current_rect.get_dim('x')) 84 | stretch_opt = (False, True) 85 | if self.current_dir == '+y': 86 | new_rect.align('cb', ref_rect=self.current_rect, ref_handle=self.current_handle) 87 | else: 88 | new_rect.align('ct', ref_rect=self.current_rect, ref_handle=self.current_handle) 89 | 90 | # Update the current rectangle pointer and stretch it to the desired location 91 | self.loc['rect_list'].append(new_rect) 92 | self.current_rect = new_rect 93 | self.current_rect.stretch(target_handle=self.current_handle, 94 | offset=loc, 95 | stretch_opt=stretch_opt) 96 | 97 | # Create parallel shields 98 | rect_1 = self.gen.add_rect(layer=self.current_rect.layer) 99 | rect_2 = self.gen.add_rect(layer=self.current_rect.layer) 100 | 101 | # If horizontal route 102 | if self.current_handle[1] in ['r', 'l']: 103 | rect_1.align('ll', new_rect, 'ul', offset=(0, parallel_spacing)) 104 | rect_2.align('ul', new_rect, 'll', offset=(0, -parallel_spacing)) 105 | rect_1.set_dim('y', width) 106 | rect_2.set_dim('y', width) 107 | dir = 'r' 108 | length = new_rect.ur.x - new_rect.ll.x 109 | # If vertical route 110 | else: 111 | rect_1.align('lr', new_rect, 'll', offset=(-parallel_spacing, 0)) 112 | rect_2.align('ll', new_rect, 'lr', offset=(parallel_spacing, 0)) 113 | rect_1.set_dim('x', width) 114 | rect_2.set_dim('x', width) 115 | dir = 't' 116 | length = new_rect.ur.y - new_rect.ll.y 117 | 118 | rect_1.stretch(dir, new_rect, dir) 119 | rect_2.stretch(dir, new_rect, dir) 120 | 121 | # Create perpendicular shields 122 | 123 | for layer in shield_layers: 124 | perpendicular_stripes = [] 125 | i = 0 126 | 127 | while (i + 1) * width + i * perpendicular_pitch < length: 128 | g_temp = self.gen.add_rect(layer) 129 | if dir == 'r': 130 | g_temp.set_dim('x', width) 131 | g_temp.align('ul', rect_1, 'ul', offset=((width + perpendicular_pitch) * i, 0)) 132 | g_temp.stretch('b', rect_2, 'b') 133 | else: 134 | g_temp.set_dim('y', width) 135 | g_temp.align('ll', rect_1, 'll', offset=(0, (width + perpendicular_pitch) * i)) 136 | g_temp.stretch('r', rect_2, 'r') 137 | self.gen.connect_wires(g_temp, rect_1) 138 | self.gen.connect_wires(g_temp, rect_2) 139 | perpendicular_stripes.append(g_temp) 140 | i += 1 141 | 142 | return self 143 | 144 | def shield_router(self, 145 | start_layer: str, 146 | perpendicular_pitch: float, 147 | parallel_spacing: float, 148 | start_width: float, 149 | start_pt: Tuple, 150 | shield_layers: list, 151 | start_dir: str, 152 | enc_style: str = 'uniform', 153 | prim: bool = False 154 | ): 155 | """ 156 | Creates a shielded route network that contains all the points added by add_route_points. 157 | 158 | Notes 159 | ----- 160 | * Calls new_route_from_location for the user -- no need to call it beforehand. 161 | 162 | Parameters 163 | ---------- 164 | start_layer : str 165 | The layer to start routing on 166 | perpendicular_pitch : float 167 | The pitch between the perpendicular shielding stripes 168 | parallel_spacing : float 169 | The pitch between the parallel shields 170 | start_width : float 171 | The width to start routing with 172 | start_pt : Tuple 173 | The point to start routing from 174 | shield_layers : list 175 | A list of layers to route perpendicular shields on 176 | start_dir : str = '+x' 177 | The direction to start routing in 178 | enc_style : str 179 | Via enclosure style to use 180 | prim ; bool 181 | True to use primitive vias, False not to 182 | 183 | Returns 184 | ------- 185 | self : WgRouter 186 | returns itself so that route segments can be easily chained together 187 | """ 188 | points = self.route_points 189 | self.new_route_from_location(start_pt, start_dir, start_layer, start_width) 190 | self.route_point_dict[start_pt] = start_width 191 | 192 | # Route the signal 193 | self.cardinal_router(enc_style=enc_style, prim=prim, clear_route=False) 194 | manh = self.manhattanize_point_list(start_dir, (start_pt, start_layer), points) 195 | 196 | # Create and route routing networks for shields 197 | router1, _, _ = self.cardinal_helper(self, manh, start_pt, start_dir, start_layer, parallel_spacing) 198 | router2, _, _ = self.cardinal_helper(self, manh, start_pt, start_dir, start_layer, -parallel_spacing) 199 | 200 | router1.cardinal_router(enc_style=enc_style, prim=prim) 201 | router2.cardinal_router(enc_style=enc_style, prim=prim) 202 | 203 | # Find all parallel shield pairs to be connected by perpendicular shields ignoring rectangles created for vias 204 | max_w = max(self.route_point_dict.values()) 205 | 206 | router1_rects = [i for i in router1.loc['rect_list'][1:] if 207 | round(i.ur.x - i.ll.x, 3) > max_w or round(i.ur.y - i.ll.y, 3) > max_w] 208 | router2_rects = [i for i in router2.loc['rect_list'][1:] if 209 | round(i.ur.x - i.ll.x, 3) > max_w or round(i.ur.y - i.ll.y, 3) > max_w] 210 | 211 | shield_pairs = list(zip(router1_rects, router2_rects)) 212 | 213 | # Iterate over each pair of shields 214 | for i in range(len(shield_pairs)): 215 | rect_1 = shield_pairs[i][0] 216 | rect_2 = shield_pairs[i][1] 217 | rects = [rect_1, rect_2] 218 | 219 | # If horizontal trace 220 | if rect_1.ur.x - rect_1.ll.x > rect_1.ur.y - rect_1.ll.y: 221 | top = max(rects, key=lambda x: x.ur.y) 222 | bottom = min(rects, key=lambda x: x.ll.y) 223 | right = min(rects, key=lambda x: x.ur.x) 224 | start = top.ll.x 225 | 226 | # Iterate over length of shield traces to add perpendicular traces at the given pitch 227 | j = 0 228 | width = self.route_point_dict[tuple(manh[i + 1][0])] 229 | while start + (j + 1) * width + j * perpendicular_pitch + 1 < right.ur.x: 230 | g_temp = self.gen.add_rect(shield_layers[0], virtual=True) 231 | 232 | # Align trace with top shield and stretch to bottom shield if it overlaps with both shields 233 | g_temp.set_dim('x', width) 234 | g_temp.align('ul', top, 'ul', offset=((width + perpendicular_pitch) * j + .5, 0)) 235 | g_temp.stretch('b', bottom, 'b') 236 | 237 | if Rectangle.overlap(g_temp, top) and Rectangle.overlap(g_temp, bottom): 238 | for layer in shield_layers: 239 | g_temp = self.gen.copy_rect(g_temp, virtual=False, layer=layer) 240 | self.gen.connect_wires(g_temp, rect_1) 241 | self.gen.connect_wires(g_temp, rect_2) 242 | 243 | j += 1 244 | 245 | # If vertical trace 246 | else: 247 | top = min(rects, key=lambda x: x.ur.y) 248 | left = min(rects, key=lambda x: x.ll.x) 249 | right = max(rects, key=lambda x: x.ur.x) 250 | 251 | start = left.ll.y 252 | 253 | # Iterate over length of shield traces to add perpendicular traces at the given pitch 254 | j = 0 255 | width = self.route_point_dict[tuple(manh[i + 1][0])] 256 | while start + (j + 1) * width + j * perpendicular_pitch + 1 < top.ur.y: 257 | g_temp = self.gen.add_rect(shield_layers[0], virtual=True) 258 | 259 | # Align trace with left shield and stretch to right shield if it overlaps with both shields 260 | g_temp.set_dim('y', width) 261 | g_temp.align('ll', left, 'll', offset=(0, (width + perpendicular_pitch) * j + .5)) 262 | g_temp.stretch('r', right, 'r') 263 | 264 | if Rectangle.overlap(g_temp, left) and Rectangle.overlap(g_temp, right): 265 | for layer in shield_layers: 266 | g_temp = self.gen.copy_rect(g_temp, virtual=False, layer=layer) 267 | self.gen.connect_wires(g_temp, rect_1) 268 | self.gen.connect_wires(g_temp, rect_2) 269 | 270 | j += 1 271 | 272 | return self 273 | 274 | def diff_pair_router(self, 275 | start_layer: str, 276 | parallel_spacing: float, 277 | start_width: float, 278 | start_pt: Tuple, 279 | start_dir: str = '+x', 280 | enc_style: str = 'uniform', 281 | prim: bool = False 282 | ): 283 | """ 284 | Creates a differential pair route network, assuming the points added by 285 | add_route_points correspond to the center of the differential pair. 286 | 287 | Parameters 288 | ---------- 289 | start_layer : str 290 | The layer to start routing on 291 | parallel_spacing : float 292 | The pitch between the parallel shields 293 | start_width : float 294 | The width to start routing with 295 | start_pt : Tuple 296 | The point to start routing from 297 | start_dir : str = '+x' 298 | The direction to start routing in 299 | enc_style : str 300 | Via enclosure style to use 301 | prim : bool 302 | True to use primitive vias 303 | 304 | Returns 305 | ------- 306 | self : WgRouter 307 | returns itself so that route segments can be easily chained together 308 | """ 309 | points = self.route_points 310 | manh = self.manhattanize_point_list(start_dir, (start_pt, start_layer), points) 311 | self.route_point_dict[start_pt] = start_width 312 | 313 | # Include new route points created by manhattanize_point_list in route_point_dict 314 | for i in range(len(manh)): 315 | point = manh[i] 316 | if tuple(point[0]) not in self.route_point_dict: 317 | if i != 0: 318 | self.route_point_dict[tuple(point[0])] = self.route_point_dict[tuple(manh[i - 1][0])] 319 | 320 | # Create and route routing networks for diff pair 321 | router1, _, _ = self.cardinal_helper(self, manh, start_pt, start_dir, start_layer, parallel_spacing / 2) 322 | router2, _, _ = self.cardinal_helper(self, manh, start_pt, start_dir, start_layer, -parallel_spacing / 2) 323 | 324 | router1.cardinal_router(enc_style=enc_style, prim=prim) 325 | router2.cardinal_router(enc_style=enc_style, prim=prim) 326 | 327 | # Clear instance variables for future routes 328 | self.route_points = [] 329 | self.route_point_dict = {} 330 | 331 | return self 332 | 333 | def bus_router(self, 334 | start_layer: str, 335 | parallel_spacing: float, 336 | bus_size: int, 337 | start_width: float, 338 | start_pt: Tuple, 339 | start_dir: str = '+x', 340 | enc_style: str = 'uniform', 341 | prim: bool = False 342 | ): 343 | """ 344 | Creates a bus route network, assuming the points added by add_route_points correspond 345 | to the center of the bus route. 346 | 347 | Parameters 348 | ---------- 349 | start_layer : str 350 | The layer to start routing on 351 | parallel_spacing : float 352 | The pitch between the parallel shields 353 | bus_size : int 354 | Size of bus (number of routes) 355 | start_width : float 356 | The width to start routing with 357 | start_pt : Tuple 358 | The point to start routing from 359 | start_dir : str = '+x' 360 | The direction to start routing in 361 | enc_style : str 362 | Via enclosure style to use 363 | prim : bool 364 | True to use primitive vias 365 | 366 | Returns 367 | ------- 368 | self : WgRouter 369 | returns itself so that route segments can be easily chained together 370 | """ 371 | # Manhattanize center path 372 | points = self.route_points 373 | manh = self.manhattanize_point_list(start_dir, (start_pt, start_layer), points) 374 | self.route_point_dict[start_pt] = start_width 375 | 376 | # Include new route points created by manhattanize_point_list in route_point_dict 377 | for i in range(len(manh)): 378 | point = manh[i] 379 | if tuple(point[0]) not in self.route_point_dict: 380 | if i != 0: 381 | self.route_point_dict[tuple(point[0])] = self.route_point_dict[tuple(manh[i - 1][0])] 382 | 383 | # Calculate sequence of routing directions 384 | dirs = [] 385 | 386 | for i in range(len(manh) - 1): 387 | pt0 = manh[i] 388 | pt1 = manh[i + 1] 389 | 390 | if pt0[0][0] > pt1[0][0]: 391 | dirs.append('-x') 392 | elif pt0[0][0] < pt1[0][0]: 393 | dirs.append('+x') 394 | elif pt0[0][1] > pt1[0][1]: 395 | dirs.append('-y') 396 | else: 397 | dirs.append('+y') 398 | 399 | top = (manh, self, start_pt) # Current "topmost" shield 400 | bottom = (manh, self, start_pt) # Current "bottommost" shield 401 | sign = 1 # Used to determine which side of center 402 | 403 | # If the bus width is odd, add a route in the center 404 | if bus_size % 2 == 1: 405 | self.new_route_from_location(start_pt, start_dir, start_layer, 0.5) 406 | self.cardinal_router(enc_style=enc_style, prim=prim, clear_route=False) 407 | num_iters = bus_size - 1 408 | # If even bus width, the center is between two routes 409 | else: 410 | num_iters = bus_size 411 | 412 | # Determine initial offset direction of routes from center 413 | if start_pt[0] > manh[1][0][0]: 414 | start = (0, -1) 415 | elif start_pt[0] < manh[1][0][0]: 416 | start = (0, 1) 417 | elif start_pt[1] > manh[1][0][1]: 418 | start = (1, 0) 419 | else: 420 | start = (-1, 0) 421 | 422 | for j in range(num_iters): 423 | manh = top[0] if sign == 1 else bottom[0] 424 | router_temp = top[1] if sign == 1 else bottom[1] 425 | temp_start = top[2] if sign == 1 else bottom[2] 426 | 427 | # If the bus width is even, the distance from the first two routes from center is half the spacing 428 | if bus_size % 2 == 0 and (j == 0 or j == 1): 429 | spacing = parallel_spacing / 2 430 | # If odd bus width, the distance from the center route is the full spacing amount 431 | else: 432 | spacing = parallel_spacing 433 | 434 | # Create and route routing network for this signal 435 | router, manh, shield_start = self.cardinal_helper(router_temp, manh, temp_start, start_dir, start_layer, sign * spacing, dirs=dirs, start=start) 436 | 437 | # Update "topmost" and "bottommost" routes 438 | if sign == 1: 439 | top = (manh, router, shield_start[0]) 440 | else: 441 | bottom = (manh, router, shield_start[0]) 442 | 443 | router.cardinal_router(enc_style=enc_style, prim=prim, clear_route=False) 444 | 445 | # Switch to opposite side of center 446 | sign = -sign 447 | 448 | # Clear instance variables for future routes 449 | self.route_points = [] 450 | self.route_point_dict = {} 451 | 452 | return self 453 | 454 | def bfs_router(self, 455 | start : Union[Tuple[float, float], XY], 456 | end : Union[Tuple[float, float], XY], 457 | start_layer: str, 458 | end_layer: str, 459 | obstructions : List[Rectangle], 460 | layers : list, 461 | routing_ll: Tuple[float, float] = None, 462 | routing_ur: Tuple[float, float] = None 463 | ): 464 | """ 465 | Given start and end points and a list of obstructions, routes from start to end round the obstructions. 466 | 467 | Notes 468 | ----- 469 | * Perform breadth-first search to find shortest path around obstructions 470 | * For a given 2D array grid, coordinate (i, j) is located at grid[j][i]. 471 | * 'O' denotes an obstruction at a grid square, 'S' denotes the start square, and 'E' denotes the end layer 472 | 473 | Parameters 474 | ---------- 475 | start : Union[Tuple[float, float], XY] 476 | The point to start routing from 477 | end : Union[Tuple[float, float], XY] 478 | The point to end routing on 479 | start_layer : str 480 | The layer to start routing on 481 | end_layer : str 482 | The layer to end routing on 483 | obstructions : List[Rectangle] 484 | List of obstructions that router should avoid 485 | layers : list 486 | List of layers to use when routing 487 | routing_ll : Tuple[float] = None 488 | If provided, provides lower left coordinate of area permitted to route in 489 | routing_ur : Tuple[float] = None 490 | If provided, provides upper right coordinate of area permitted to route in 491 | 492 | Returns 493 | ------- 494 | self : WgRouter 495 | returns itself so that route segments can be easily chained together 496 | """ 497 | 498 | # Reset instance variables 499 | self.grids = {} # Dictionary containing grid 2D array for each layer 500 | self.dims = {} # Dictionary of array dimensions for each layer 501 | self.routing_layers = layers # List of layers to route on 502 | 503 | self.route_points = [] 504 | self.route_point_dict = {} 505 | 506 | # Snap all input coordinates to grid 507 | start_spacing = self.config[start_layer]['spacing'] 508 | start = (round(start[0] / start_spacing) * start_spacing, round(start[1] / start_spacing) * start_spacing) 509 | 510 | end_spacing = self.config[end_layer]['spacing'] 511 | end = (round(end[0] / end_spacing) * end_spacing, round(end[1] / end_spacing) * end_spacing) 512 | 513 | # If routing area not defined, define it using bounds of start and end coordinates 514 | if not (routing_ll and routing_ur): 515 | for layer in layers: 516 | # Determine grid size and initialize grid 517 | x = round((max([end[0], start[0]]) - min([end[0], start[0]])) / self.config[layer]['spacing']) + 1 518 | y = round((max([end[1], start[1]]) - min([end[1], start[1]])) / self.config[layer]['spacing']) + 1 519 | 520 | grid = [[None for _ in range(x)] for _ in range(y)] 521 | 522 | self.grids[layer] = grid 523 | self.dims[layer] = (x, y) 524 | 525 | start_dim = self.dims[start_layer] 526 | end_dim = self.dims[end_layer] 527 | 528 | # Determine lower left and upper right coordinates based on start and end points 529 | if end[0] > start[0]: 530 | if end[1] > start[1]: 531 | start_coord = (0, 0) 532 | end_coord = (end_dim[0] - 1, end_dim[1] - 1) 533 | ll_pos = start 534 | ur_pos = end 535 | else: 536 | start_coord = (0, start_dim[1] - 1) 537 | end_coord = (end_dim[0] - 1, 0) 538 | ll_pos = (start[0], end[1]) 539 | ur_pos = (end[0], start[1]) 540 | else: 541 | if end[1] > start[1]: 542 | start_coord = (start_dim[0] - 1, 0) 543 | end_coord = (0, end_dim[1] - 1) 544 | ll_pos = (end[0], start[1]) 545 | ur_pos = (start[0], end[1]) 546 | else: 547 | start_coord = (start_dim[0] - 1, start_dim[1] - 1) 548 | end_coord = (0, 0) 549 | ll_pos = end 550 | ur_pos = start 551 | 552 | # If routing area has been provided 553 | else: 554 | for layer in layers: 555 | layer_spacing = self.config[layer]['spacing'] 556 | ur_temp = (round(routing_ur[0] / layer_spacing) * layer_spacing, round(routing_ur[1] / layer_spacing) * layer_spacing) 557 | ll_temp = (round(routing_ll[0] / layer_spacing) * layer_spacing, round(routing_ll[1] / layer_spacing) * layer_spacing) 558 | 559 | x = round((ur_temp[0] - ll_temp[0]) / layer_spacing) + 2 560 | y = round((ur_temp[1] - ll_temp[1]) / layer_spacing) + 2 561 | 562 | grid = [[None for _ in range(x)] for _ in range(y)] 563 | 564 | self.grids[layer] = grid 565 | self.dims[layer] = (x, y) 566 | 567 | ll_pos = routing_ll 568 | ur_pos = routing_ur 569 | 570 | # Determine start and end grid coordinates relative to given routing area 571 | start_coord = (round((start[0] - ll_pos[0]) / self.config[start_layer]['spacing']), 572 | round((start[1] - ll_pos[1]) / self.config[start_layer]['spacing'])) 573 | end_coord = (round((end[0] - ll_pos[0]) / self.config[end_layer]['spacing']), 574 | round((end[1] - ll_pos[1]) / self.config[end_layer]['spacing'])) 575 | 576 | # Mark start coordinate as 'S', end coordinate as 'E' 577 | self.grids[start_layer][start_coord[1]][start_coord[0]] = 'S' 578 | self.grids[end_layer][end_coord[1]][end_coord[0]] = 'E' 579 | 580 | 581 | obstructions = obstructions + self.loc['rect_list'] 582 | # Initialize obstructions on the grid 583 | for rect in obstructions: 584 | # If the obstructions are in the routing area 585 | if rect and Rectangle.overlap(rect, Rectangle((ll_pos, ur_pos), '')) and rect.layer in layers: 586 | rel_ll_coord = (rect.ll.x - ll_pos[0], rect.ll.y - ll_pos[1]) 587 | rel_ur_coord = (rect.ur.x - ll_pos[0], rect.ur.y - ll_pos[1]) 588 | 589 | # Determine grid coordinates of obstruction, fill in each obstructed grid square with 'O' 590 | ll = round(rel_ll_coord[0] / self.config[rect.layer]['spacing']), round(rel_ll_coord[1] / self.config[rect.layer]['spacing']) 591 | ur = round(rel_ur_coord[0] / self.config[rect.layer]['spacing']), round(rel_ur_coord[1] / self.config[rect.layer]['spacing']) 592 | 593 | for j in range(max([ll[1], 0]), min([ur[1] + 1, self.dims[rect.layer][1]])): 594 | for i in range(max([ll[0], 0]), min([ur[0] + 1, self.dims[rect.layer][0]])): 595 | self.grids[rect.layer][j][i] = 'O' 596 | 597 | # Perform first half of wave propagation algorithm to label each grid square 598 | self.label_node(start_layer, start_coord[0], start_coord[1]) 599 | 600 | curr_node = end_coord + (end_layer,) 601 | path = [curr_node] 602 | grid = self.grids[curr_node[2]] 603 | 604 | # for i in self.grids: 605 | # print("GRID") 606 | # for j in self.grids[i]: 607 | # print(j) 608 | 609 | 610 | visited = copy.deepcopy(self.grids) 611 | 612 | # Perform second half of wave propagation algorithm 613 | # Back propagate from end point by finding the minimum-value neighbor at each iteration 614 | 615 | while grid[curr_node[1]][curr_node[0]] != 1: 616 | neighbors = self.get_neighbors(curr_node[2], curr_node[0], curr_node[1]) 617 | neighbors = [i for i in neighbors if type(self.grids[i[2]][i[1]][i[0]]) == int and visited[i[2]][i[1]][i[0]] != 'V'] 618 | curr_node = min(neighbors, key=lambda x: self.grids[x[2]][x[1]][x[0]]) 619 | visited[curr_node[2]][curr_node[1]][curr_node[0]] = 'V' 620 | grid = self.grids[curr_node[2]] 621 | path.append(curr_node) 622 | 623 | # Convert grid coordinates to real coordinates 624 | real_path = [((round(round(ll_pos[0] / self.config[i[2]]['spacing']) * self.config[i[2]]['spacing'] + 625 | self.config[i[2]]['spacing'] * i[0], 3), 626 | round(round(ll_pos[1] / self.config[i[2]]['spacing']) * self.config[i[2]]['spacing'] + 627 | self.config[i[2]]['spacing'] * i[1], 3)), i[2]) for i in path][::-1] 628 | 629 | next_pt = real_path[0][0] 630 | 631 | # Determine start direction 632 | if next_pt[0] > start[0]: 633 | start_dir = '+x' 634 | elif next_pt[0] < start[0]: 635 | start_dir = '-x' 636 | elif next_pt[1] > start[1]: 637 | start_dir = '+y' 638 | else: 639 | start_dir = '-y' 640 | 641 | real_path = self.manhattanize_point_list(start_dir, (start, start_layer), real_path) 642 | 643 | # del_idx = [] 644 | # for i in range(len(real_path) - 1): 645 | # if round(real_path[i][0][0], 3) == round(real_path[i + 1][0][0], 3) and round(real_path[i][0][1], 3) == round(real_path[i + 1][0][1], 3): 646 | # del_idx.append(i) 647 | # 648 | # real_path = [real_path[i] for i in range(len(real_path)) if i not in del_idx] 649 | 650 | for i in range(len(real_path) - 2): 651 | pt0 = real_path[i] 652 | pt1 = real_path[i + 1] 653 | pt2 = real_path[i + 2] 654 | 655 | if pt0[0][0] == pt1[0][0] == pt2[0][0] and (pt0[0][1] < pt1[0][1] > pt2[0][1] or pt0[0][1] > pt1[0][1] 656 | < pt2[0][1]) and pt0[1] == pt1[1] == pt2[1]: 657 | pt1 = ((pt1[0][0], pt2[0][1]), pt1[1]) 658 | elif pt0[0][1] == pt1[0][1] == pt2[0][1] and (pt0[0][0] < pt1[0][0] > pt2[0][0] or pt0[0][0] > pt1[0][0] 659 | < pt2[0][0]) and pt0[1] == pt1[1] == pt2[1]: 660 | pt1 = ((pt2[0][0], pt1[0][1]), pt1[1]) 661 | 662 | real_path[i + 1] = pt1 663 | 664 | for point in real_path: 665 | add_width = False if point[0] in self.route_point_dict else True 666 | self.add_route_points([point[0]], point[1], self.config[point[1]]['width'], add_width=add_width) 667 | 668 | # Route points 669 | self.new_route_from_location(start, start_dir, start_layer, self.config[start_layer]['width']) 670 | self.cardinal_router(prim=True) 671 | 672 | return self 673 | 674 | def find_adjacent(self, layer1, layer2, i, j): 675 | """Determine the corresponding grid square to a given grid square on an adjacent layer""" 676 | spacing1 = self.config[layer1]['spacing'] 677 | spacing2 = self.config[layer2]['spacing'] 678 | return round((i * spacing1) / spacing2), round((j * spacing1) / spacing2) 679 | 680 | def label_node(self, curr_layer, i, j): 681 | h = [((i, j, curr_layer), 0)] # FIFO queue for breadth-first search 682 | 683 | # While there are still grid squares to label ('E' hasn't been found) 684 | while h: 685 | # Pop front of queue 686 | # item = ((i, j, layer), idx) 687 | item = h[0] 688 | h = h[1:] 689 | i = item[0][0] 690 | j = item[0][1] 691 | curr_layer = item[0][2] 692 | grid = self.grids[curr_layer] 693 | elem = grid[j][i] 694 | 695 | if elem == 'E': # found endpoint (and therefore shortest path), no need to continue searching 696 | return 697 | elif elem == 'O' or elem and elem != 'S': # Cannot label obstructed or already labeled grid squares 698 | continue 699 | elif not elem: # Label unlabeled square 700 | grid[j][i] = item[1] 701 | 702 | # Add all of this grid square's unlabeled neighbors to queue with an incremented idx 703 | for neighbor in self.get_neighbors(curr_layer, i, j): 704 | if not self.grids[neighbor[2]][neighbor[1]][neighbor[0]] or self.grids[neighbor[2]][neighbor[1]][neighbor[0]] == 'E': 705 | h.append((neighbor, item[1] + 1)) 706 | 707 | def get_neighbors(self, layer, i, j): 708 | """Find all of a grid square's neighbor grid squares""" 709 | grid = self.grids[layer] 710 | 711 | all_layers = tech_info['metal_tech']['routing'] 712 | layer_idx = [i for i in range(len(all_layers)) if all_layers[i] == layer][0] 713 | neighboring_layers = [] 714 | if layer_idx + 1 < len(all_layers): 715 | neighboring_layers.append(all_layers[layer_idx + 1]) 716 | if layer_idx - 1 >= 0: 717 | neighboring_layers.append(all_layers[layer_idx - 1]) 718 | 719 | neighboring_layers = [l for l in neighboring_layers if l in self.routing_layers] 720 | 721 | neighbors = [] 722 | gridY = len(grid) 723 | gridX = len(grid[0]) 724 | 725 | direction = self.config[layer]['direction'] 726 | 727 | # If horizontal layer, there are only horizontal neighbors 728 | if direction == 'x' or direction == 'xy': 729 | if i + 1 < gridX: 730 | neighbors.append((i + 1, j, layer)) 731 | if i - 1 >= 0: 732 | neighbors.append((i - 1, j, layer)) 733 | 734 | # If vertical layer, there are only vertical neighbors 735 | if direction == 'y' or direction == 'xy': 736 | if j + 1 < gridY: 737 | neighbors.append((i, j + 1, layer)) 738 | if j - 1 >= 0: 739 | neighbors.append((i, j - 1, layer)) 740 | 741 | # Find all neighboring grid squares on neighboring layers 742 | for l in neighboring_layers: 743 | if l != layer: 744 | i2, j2 = self.find_adjacent(layer, l, i, j) 745 | if i2 < self.dims[l][0] and j2 < self.dims[l][1]: 746 | neighbors.append((i2, j2, l)) 747 | 748 | return neighbors 749 | -------------------------------------------------------------------------------- /ACG/AutoRouter.py: -------------------------------------------------------------------------------- 1 | from .AyarLayoutGenerator import AyarLayoutGenerator 2 | from .Rectangle import Rectangle 3 | from .XY import XY 4 | from .tech import tech_info 5 | from typing import Tuple, Union, Optional, List, Dict 6 | 7 | 8 | class EZRouter: 9 | """ 10 | The EZRouter class provides a number of methods to automatically generate simple wire routes in ACG. This class 11 | does not require the use of tracks 12 | """ 13 | valid_directions = ['+x', '-x', '+y', '-y'] 14 | valid_handles = ['cr', 'cl', 'cb', 'ct', 'll' 'ul', 'lr', 'ur'] 15 | 16 | def __init__(self, 17 | gen_cls: AyarLayoutGenerator, 18 | start_rect: Optional[Rectangle] = None, 19 | start_direction: Optional[str] = None, 20 | config: Optional[dict] = None 21 | ): 22 | """ 23 | Expects an ACG layout generator as input. This generator class has its shape creation methods called after the 24 | route is completed 25 | 26 | Parameters 27 | ---------- 28 | gen_cls : AyarLayoutGenerator 29 | Layout generator class that this Autorouter will be drawing in 30 | start_rect : Rectangle 31 | The rectangle we will be starting the route from 32 | start_direction : str 33 | '+x', '-x', '+y', '-y' for the direction the route will start from 34 | config : Optional[dict] 35 | dictionary of configuration variables that will set router defaults 36 | """ 37 | # Init generator and tech information 38 | self.gen: AyarLayoutGenerator = gen_cls 39 | self.tech = self.gen.tech 40 | self.config = tech_info['metal_tech']['router'] 41 | if config: 42 | self.config.update(config) # Update the default settings with your own 43 | 44 | # Init core router state variables 45 | self._current_dir = None 46 | self._current_handle = None 47 | self.layer = None 48 | self.current_rect = None 49 | 50 | # Location dictionary to store the running components in the route 51 | self.loc = dict( 52 | route_list=[self.current_rect], 53 | rect_list=[self.current_rect], 54 | via_list=[], 55 | ) 56 | 57 | self.route_points = [] 58 | self.route_point_dict = {} 59 | 60 | # to determine offset of shield_1 from center 61 | self.shield_dict = { 62 | '+x': { 63 | '+x': (0, 1), 64 | '-x': (0, 1), 65 | '+y': (-1, 1), 66 | '-y': (1, 1) 67 | }, 68 | '-x': { 69 | '+x': (0, -1), 70 | '-x': (0, -1), 71 | '+y': (-1, -1), 72 | '-y': (1, -1) 73 | }, 74 | '+y': { 75 | '+x': (-1, 1), 76 | '-x': (-1, -1), 77 | '+y': (-1, 0), 78 | '-y': (-1, 0) 79 | }, 80 | '-y': { 81 | '+x': (1, 1), 82 | '-x': (1, -1), 83 | '+y': (1, 0), 84 | '-y': (1, 0) 85 | } 86 | } 87 | 88 | # If the user provided information for a new route, create one 89 | if start_rect and start_direction: 90 | self.new_route(start_rect=start_rect, 91 | start_direction=start_direction) 92 | 93 | ''' Set up properties to perform run-time checking on router state variables ''' 94 | 95 | @property 96 | def current_dir(self) -> str: 97 | return self._current_dir 98 | 99 | @current_dir.setter 100 | def current_dir(self, value: str): 101 | if value in EZRouter.valid_directions: 102 | self._current_dir = value 103 | else: 104 | raise ValueError(f'direction {value} is not valid') 105 | 106 | @property 107 | def current_handle(self) -> str: 108 | return self._current_handle 109 | 110 | @current_handle.setter 111 | def current_handle(self, value: str): 112 | if value in EZRouter.valid_handles: 113 | self._current_handle = value 114 | else: 115 | raise ValueError(f'handle {value} is not valid') 116 | 117 | def new_route(self, 118 | start_rect: Rectangle, 119 | start_direction: str, 120 | ) -> 'EZRouter': 121 | """ 122 | Sets up the state variables to create a route paths. Requires a starting rectangle 123 | and starting routing direction. 124 | 125 | Parameters 126 | ---------- 127 | start_rect : Rectangle 128 | The rectangle we will be starting the route from 129 | start_direction : str 130 | '+x', '-x', '+y', '-y' for the direction the route will start from 131 | 132 | Returns 133 | ------- 134 | self : EZRouter 135 | Return self to make it easy to cascade connections 136 | """ 137 | # State variables for where the route will be going 138 | self.current_rect = self.gen.copy_rect(start_rect, virtual=False) 139 | self.current_dir = start_direction 140 | self.layer = start_rect.layer 141 | self._set_handle_from_dir(direction=start_direction) 142 | 143 | if start_direction[1] == 'x': 144 | width = start_rect.ur.y - start_rect.ll.y 145 | elif start_direction[1] == 'y': 146 | width = start_rect.ur.x - start_rect.ll.x 147 | 148 | current_point = tuple(self.current_rect[self.current_handle].xy) 149 | self.route_point_dict[current_point] = width 150 | 151 | # Reset location dict 152 | self.loc = dict( 153 | route_list=[self.current_rect], 154 | rect_list=[self.current_rect], 155 | via_list=[], 156 | ) 157 | 158 | return self 159 | 160 | def new_route_from_location(self, 161 | start_loc: Union[Tuple[float, float], XY], 162 | start_direction: str, 163 | start_layer: Union[str, Tuple[str, str]], 164 | width: float, 165 | length: Optional[float] = None, 166 | ) -> 'EZRouter': 167 | """ 168 | This method enables you to start a route from an arbitrary location with the specified wire layer, 169 | width, and length. If a length is not provided, it will use the minimium grid resolution to minimize 170 | the chance of DRC issues 171 | 172 | Parameters 173 | ---------- 174 | start_loc : 175 | location where the new route will start 176 | start_direction : str 177 | direction where this route will point 178 | start_layer : str 179 | layer where the first route segment will be placed 180 | width : float 181 | width of the new path 182 | length : Optional[float] 183 | If a length is provided, the new route segment will be extended in the direction opposite to the 184 | current routing direction 185 | 186 | Returns 187 | ------- 188 | self : EZRouter 189 | Return self to make it easy to cascade connections 190 | """ 191 | self.current_dir = start_direction 192 | self._set_handle_from_dir(direction=start_direction) 193 | self.current_rect = self.gen.add_rect(layer=start_layer) 194 | self.layer = self.current_rect.layer 195 | if self.current_dir == '+x' or self.current_dir == '-x': 196 | self.current_rect.set_dim('y', width) 197 | if not length: 198 | self.current_rect.set_dim('x', self.gen.grid.resolution * 2) 199 | else: 200 | self.current_rect.set_dim('x', length) 201 | else: 202 | self.current_rect.set_dim('x', width) 203 | if not length: 204 | self.current_rect.set_dim('y', self.gen.grid.resolution * 2) 205 | else: 206 | self.current_rect.set_dim('y', length) 207 | self.current_rect.align(self.current_handle, offset=start_loc) 208 | self.route_point_dict[(round(start_loc[0], 3), round(start_loc[1], 3))] = width 209 | return self 210 | 211 | def draw_straight_route(self, 212 | loc: Union[Tuple[float, float], XY], 213 | width: Optional[float] = None 214 | ) -> 'EZRouter': 215 | """ 216 | Routes a straight metal line from the current location of specified length. This 217 | method does not change the current routing direction. 218 | 219 | Note: This method relies on the fact that stretching a rectangle with an offset 220 | but without a reference rectangle uses the offset as an absolute reference loc 221 | 222 | Parameters 223 | ---------- 224 | loc : Optional[Tuple[float, float]] 225 | XY location to route to. This method will only route in the current direction 226 | width : Optional[float] 227 | Draws the new route segment with this width if provided 228 | 229 | Returns 230 | ------- 231 | self : EZRouter 232 | Return self to make it easy to cascade connections 233 | """ 234 | if not self.current_rect or not self.current_handle or not self.current_dir: 235 | raise ValueError('Router has not been initialized, please call new_route()') 236 | 237 | # print('===In draw_straight_route, starting rect %s, direction %s' % (self.current_rect, self.current_dir)) 238 | 239 | # Make a new rectangle and align it to the current route location 240 | new_rect = self.gen.add_rect(layer=self.current_rect.layer) 241 | # print('===In draw_straight_route, layer = %s, width = %f, loc=%s' 242 | # % (self.current_rect.layer, width, str(loc))) 243 | 244 | # Size the new rectangle to match the current route width 245 | if self.current_dir == '+x' or self.current_dir == '-x': 246 | if width: 247 | new_rect.set_dim('y', width) 248 | else: 249 | new_rect.set_dim('y', self.current_rect.get_dim('y')) 250 | stretch_opt = (True, False) 251 | if self.current_dir == '+x': 252 | new_rect.align('cl', ref_rect=self.current_rect, ref_handle=self.current_handle) 253 | else: 254 | new_rect.align('cr', ref_rect=self.current_rect, ref_handle=self.current_handle) 255 | else: 256 | if width: 257 | new_rect.set_dim('x', width) 258 | else: 259 | new_rect.set_dim('x', self.current_rect.get_dim('x')) 260 | stretch_opt = (False, True) 261 | if self.current_dir == '+y': 262 | new_rect.align('cb', ref_rect=self.current_rect, ref_handle=self.current_handle) 263 | else: 264 | new_rect.align('ct', ref_rect=self.current_rect, ref_handle=self.current_handle) 265 | 266 | # Update the current rectangle pointer and stretch it to the desired location 267 | self.loc['rect_list'].append(new_rect) 268 | self.current_rect = new_rect 269 | self.current_rect.stretch(target_handle=self.current_handle, 270 | offset=loc, 271 | stretch_opt=stretch_opt) 272 | return self 273 | 274 | def draw_via(self, 275 | layer: Union[str, Tuple[str, str]], 276 | direction: str, 277 | enc_style: str = 'uniform', 278 | out_width: Optional[float] = None, 279 | size: Optional[Tuple[int, int]] = None, 280 | enc_bot: Optional[List[float]] = None, 281 | enc_top: Optional[List[float]] = None, 282 | prim: bool = True 283 | ) -> 'EZRouter': 284 | """ 285 | This method adds a via at the current location to the user provided layer. Cannot 286 | move by more than one layer at a time 287 | 288 | This method will change the current rectangle and can change the current routing 289 | direction 290 | 291 | Parameters 292 | ---------- 293 | layer : Union[str, Tuple[str, str]] 294 | layer or lpp of the metal to via up/down to 295 | direction : str 296 | '+x', '-x', '+y', '-y' for the direction the new route segment will start from 297 | out_width : Optional[float] 298 | width of the output wire to be drawn on the new metal layer 299 | enc_style : str 300 | 'uniform' to draw uniform enclosures, 'asymm' to draw min size asymmetric enclosures 301 | size : Optional[Tuple[int, int]] 302 | number of vias to place in the array 303 | enc_bot : List[float] 304 | enclosure size of the left, right, top, bot edges of the bottom layer of the via 305 | enc_top : List[float] 306 | enclosure size of the left, right, top, bot edges of the top layer of the via 307 | prim : bool 308 | True to use primitive vias, else False 309 | 310 | Returns 311 | ------- 312 | self : AutoRouter 313 | Return self to make it easy to cascade connections 314 | """ 315 | if not self.current_rect or not self.current_handle or not self.current_dir: 316 | raise ValueError('Router has not been initialized, please call new_route()') 317 | 318 | # Create the new rectangle and align it to the end of the route 319 | new_rect = self.gen.add_rect(layer=layer) 320 | new_rect.align(target_handle='c', 321 | ref_rect=self.current_rect, 322 | ref_handle=self.current_handle) 323 | 324 | if not out_width: 325 | out_width = self.config[layer]['width'] 326 | 327 | # Match the route width of the current route 328 | if self.current_dir == '+x' or self.current_dir == '-x': 329 | new_rect.set_dim('y', size=self.current_rect.get_dim('y')) 330 | else: 331 | new_rect.set_dim('x', size=self.current_rect.get_dim('x')) 332 | 333 | # Size the new rectangle to match the output width 334 | if direction == '+x' or direction == '-x': 335 | new_rect.set_dim('y', out_width) 336 | else: 337 | new_rect.set_dim('x', out_width) 338 | 339 | if self.current_dir[1] == 'x' and direction[1] == 'x': 340 | new_rect.set_dim('x', self.current_rect.get_dim('y')) 341 | 342 | elif self.current_dir[1] == direction[1] == 'y': 343 | new_rect.set_dim('y', self.current_rect.get_dim('x')) 344 | 345 | # If the provided layer is the same as the current layer, turn the route 346 | # Otherwise add a new via with the calculated enclosure rules 347 | if prim and layer != self.current_rect.layer: 348 | # Add a new primitive via at the current location 349 | if self.current_rect.get_highest_layer(layer=layer) == self.current_rect.lpp: 350 | via_id = 'V' + layer + '_' + self.current_rect.layer 351 | else: 352 | via_id = 'V' + self.current_rect.layer + '_' + layer 353 | via = self.gen.add_prim_via(via_id=via_id, rect=new_rect) 354 | 355 | # If we use asymmetric via enclosures, figure out which directions should 356 | # have what enclosure size 357 | if enc_style == 'asymm': 358 | # Determine whether the current route segment is on bottom or top 359 | # Allocate the default enc params to the corresponding layer 360 | if self.current_rect.get_highest_layer(layer=layer) == self.current_rect.lpp: 361 | default_enc = self.config['V' + layer + '_' + self.current_rect.layer] 362 | 363 | # Set the enclosure for the current route segment 364 | enc_large = default_enc['asymm_enclosure_large'] 365 | enc_small = default_enc['asymm_enclosure_small'] 366 | if self.current_dir == '+x' or self.current_dir == '-x': 367 | via.set_enclosure(enc_top=[enc_large, enc_large, enc_small, enc_small]) 368 | else: 369 | via.set_enclosure(enc_top=[enc_small, enc_small, enc_large, enc_large]) 370 | 371 | # Set the enclosure for the next route segment 372 | enc_large = default_enc['asymm_enclosure_large'] 373 | enc_small = default_enc['asymm_enclosure_small'] 374 | if direction == '+x' or direction == '-x': 375 | via.set_enclosure(enc_bot=[enc_large, enc_large, enc_small, enc_small]) 376 | else: 377 | via.set_enclosure(enc_bot=[enc_small, enc_small, enc_large, enc_large]) 378 | else: 379 | default_enc = self.config['V' + self.current_rect.layer + '_' + layer] 380 | 381 | # Set the enclosure for the current route segment 382 | enc_large = default_enc['asymm_enclosure_large'] 383 | enc_small = default_enc['asymm_enclosure_small'] 384 | if self.current_dir == '+x' or self.current_dir == '-x': 385 | via.set_enclosure(enc_bot=[enc_large, enc_large, enc_small, enc_small]) 386 | else: 387 | via.set_enclosure(enc_bot=[enc_small, enc_small, enc_large, enc_large]) 388 | 389 | # Set the enclosure for the next route segment 390 | enc_large = default_enc['asymm_enclosure_large'] 391 | enc_small = default_enc['asymm_enclosure_small'] 392 | if direction == '+x' or direction == '-x': 393 | via.set_enclosure(enc_top=[enc_large, enc_large, enc_small, enc_small]) 394 | else: 395 | via.set_enclosure(enc_top=[enc_small, enc_small, enc_large, enc_large]) 396 | 397 | new_rect_2 = self.gen.copy_rect(new_rect, layer=self.current_rect.layer) 398 | # Set via parameters 399 | if size is not None: 400 | via.size = size 401 | else: 402 | via.size = self.config[via_id]['size'] 403 | if enc_bot is not None: 404 | via.set_enclosure(enc_bot=enc_bot) 405 | if enc_top is not None: 406 | via.set_enclosure(enc_top=enc_top) 407 | 408 | if not prim: 409 | new_rect_2 = self.gen.copy_rect(new_rect, layer=self.current_rect.layer) 410 | self.gen.connect_wires(new_rect, new_rect_2) 411 | 412 | # Update the pointers for the current rect, handle, and direction 413 | self.loc['rect_list'].append(new_rect) 414 | self.current_rect = new_rect 415 | self.current_dir = direction 416 | self._set_handle_from_dir(direction) 417 | 418 | return self 419 | 420 | def draw_l_route(self, 421 | loc: Union[Tuple[float, float], XY], 422 | enc_style: str = 'uniform', 423 | in_width: Optional[float] = None, 424 | out_width: Optional[float] = None, 425 | layer: Optional[Union[str, Tuple[str, str]]] = None, 426 | enc_bot: Optional[List[float]] = None, 427 | enc_top: Optional[List[float]] = None, 428 | ) -> 'EZRouter': 429 | """ 430 | Draws an L-route from the current location to the provided location while minimizing 431 | the number of turns. 432 | 433 | Parameters 434 | ---------- 435 | loc : Union[Tuple[float, float], XY] 436 | Final location of the route 437 | enc_style : str 438 | 'uniform' to draw uniform enclosures, 'asymm' to draw min size asymmetric enclosures 439 | in_width : Optional[float] 440 | If provided, will change the first route segment to the desired width 441 | out_width : Optional[float] 442 | If provided, will set the second segment of the l-route to match this width, otherwise 443 | will maintain the same width 444 | layer : Optional[Union[str, Tuple[str, str]]] 445 | If provided will draw the second segment of the l-route on this metal layer, otherwise 446 | will stay on the same layer 447 | enc_bot : Optional[List[float]] 448 | If provided, will use these enclosure settings for the bottom layer of the via 449 | enc_top : Optional[List[float]] 450 | If provided, will use these enclosure settings for the top layer of the via 451 | 452 | Returns 453 | ------- 454 | self : AutoRouter 455 | Return self to make it easy to cascade connections 456 | """ 457 | if not self.current_rect or not self.current_handle or not self.current_dir: 458 | raise ValueError('Router has not been initialized, please call new_route()') 459 | 460 | # Draw the first straight route segment 461 | self.draw_straight_route(loc=loc, width=in_width) 462 | 463 | # Draw the via to turn the l-route 464 | # If layer is None, stay on the same layer 465 | if not layer: 466 | layer = self.current_rect.layer 467 | # If an output width is not provided, use the same as the current width 468 | if not out_width: 469 | if self.current_dir == '+x' or self.current_dir == '-x': 470 | out_width = self.current_rect.get_dim('y') 471 | else: 472 | out_width = self.current_rect.get_dim('x') 473 | # Determine the output direction 474 | if self.current_dir == '+x' or self.current_dir == '-x': 475 | if self.current_rect[self.current_handle].y < XY(loc).y: 476 | direction = '+y' 477 | else: 478 | direction = '-y' 479 | else: 480 | if self.current_rect[self.current_handle].x < XY(loc).x: 481 | direction = '+x' 482 | else: 483 | direction = '-x' 484 | self.draw_via(layer=layer, 485 | direction=direction, 486 | enc_style=enc_style, 487 | out_width=out_width, 488 | enc_top=enc_top, 489 | enc_bot=enc_bot) 490 | 491 | # Draw the final straight route segment 492 | self.draw_straight_route(loc=loc, width=out_width) 493 | 494 | return self 495 | 496 | ''' Automatic routing methods ''' 497 | 498 | def add_route_points(self, 499 | points: List[Tuple], 500 | layer: str, 501 | width: Optional[float] = None, 502 | add_width: bool = True 503 | ): 504 | """ 505 | Adds provided points to route network. 506 | 507 | Notes 508 | ----- 509 | * Stores width for each point in self.route_point_dict 510 | * No need to add start point of route network 511 | 512 | Parameters 513 | ---------- 514 | points : str 515 | A list of Tuple[float, float] or XY 516 | layer : str 517 | The layer on which to route the given points 518 | width : float 519 | The width of the route at the given points 520 | """ 521 | for point in points: 522 | p = (round(point[0], 3), round(point[1], 3)) 523 | self.route_points.append((p, layer)) 524 | if add_width: 525 | self.route_point_dict[p] = width 526 | 527 | def cardinal_helper(self, router_temp, manh, start_pt, start_dir, start_layer, offset, dirs=None, start=None): 528 | """ 529 | Helper method for cardinal router in order to create routes that are offset by some amount from a given router 530 | """ 531 | if not dirs: 532 | # Calculate sequence of routing directions 533 | dirs = [] 534 | 535 | for i in range(len(manh) - 1): 536 | pt0 = manh[i] 537 | pt1 = manh[i + 1] 538 | 539 | if pt0[0][0] > pt1[0][0]: 540 | dirs.append('-x') 541 | elif pt0[0][0] < pt1[0][0]: 542 | dirs.append('+x') 543 | elif pt0[0][1] > pt1[0][1]: 544 | dirs.append('-y') 545 | else: 546 | dirs.append('+y') 547 | 548 | # Determine initial offset direction of routes from center 549 | if not start: 550 | if start_pt[0] > manh[1][0][0]: 551 | start = (0, -1) 552 | elif start_pt[0] < manh[1][0][0]: 553 | start = (0, 1) 554 | elif start_pt[1] > manh[1][0][1]: 555 | start = (1, 0) 556 | else: 557 | start = (-1, 0) 558 | 559 | for i in range(len(dirs)): 560 | if i == 0: 561 | # Determine start point of new route relative to given route 562 | shield_start = ((start_pt[0] + offset * start[0], 563 | start_pt[1] + offset * start[1]), start_layer) 564 | 565 | # Initialize new router 566 | router = EZRouter(self.gen) 567 | router.new_route_from_location(shield_start[0], start_dir, start_layer, 0.5) 568 | else: 569 | pt0 = manh[i] 570 | # Get offset direction given previous routing direction and current routing direction 571 | direc = self.shield_dict[dirs[i - 1]][dirs[i]] 572 | 573 | # Determine new point in route based on offset and add to router 574 | point = (pt0[0][0] + offset * direc[0], 575 | pt0[0][1] + offset * direc[1]) 576 | router.add_route_points([point], pt0[1], width=router_temp.route_point_dict[pt0[0]]) 577 | 578 | # Determine final offset direction of routes from center and add final point to router 579 | if manh[-2][0][0] > manh[-1][0][0]: 580 | end = (0, -1) 581 | elif manh[-2][0][0] < manh[-1][0][0]: 582 | end = (0, 1) 583 | elif manh[-2][0][1] > manh[-1][0][1]: 584 | end = (1, 0) 585 | else: 586 | end = (-1, 0) 587 | 588 | router.add_route_points( 589 | [(manh[-1][0][0] + offset * end[0], 590 | manh[-1][0][1] + offset * end[1])], manh[-1][1], 591 | width=router_temp.route_point_dict[manh[-1][0]]) 592 | 593 | manh = router.manhattanize_point_list(start_dir, (shield_start[0], start_layer), router.route_points) 594 | 595 | return router, manh, shield_start 596 | 597 | def cardinal_router(self, 598 | points: List[Tuple] = None, 599 | relative_coords: bool = False, 600 | enc_style: str = 'uniform', 601 | prim: bool = True, 602 | clear_route: bool = True 603 | ): 604 | """ 605 | Creates a route network that contains all provided points. Any required vias use the user provided 606 | 607 | Notes 608 | ----- 609 | * This method forces the use of width and via parameters from the configuration dictionary 610 | * This method attempts to generate a manhattanized list of points that contains all of the user 611 | provided points while minimizing the number of times the direction of the route changes 612 | * Then a set of cascaded L-routes is created to connect all of the coordinates in the mahattanized point list 613 | * TODO: Make it more clear what the points datastructure is doing 614 | * TODO: Add checks to ensure we dont try to turn in impossible directions 615 | 616 | Parameters 617 | ---------- 618 | points : List[Tuple] 619 | List of (x, y, layer) points that the route will contain 620 | relative_coords : bool 621 | True if the list of coordinates are relative to the starting port's coordinate. 622 | False if the list of coordinates are absolute relative to the current Template's origin 623 | enc_style : str 624 | Via enclosure style to use 625 | prim : bool 626 | True to use primitive vias 627 | clear_route : bool 628 | True to clear self.route_point_dict and self.route_points in order to instantiate another route 629 | 630 | Returns 631 | ------- 632 | self : WgRouter 633 | returns itself so that route segments can be easily chained together 634 | """ 635 | if not self.current_rect or not self.current_handle or not self.current_dir: 636 | raise ValueError('Router has not been initialized, please call new_route()') 637 | 638 | if not points: 639 | points = self.route_points 640 | else: 641 | for point in points: 642 | self.route_point_dict[tuple(point[0])] = self.config[point[1]]['width'] 643 | 644 | current_dir = self.current_dir 645 | current_point = (self.current_rect[self.current_handle].xy, self.current_rect.layer) 646 | 647 | if relative_coords: 648 | # If passed coordinates are relative, need to add WgRouter's port location to convert to absolute coords 649 | x0, y0 = current_point[0] 650 | points = [((pt[0] + x0, pt[1] + y0), layer) for pt, layer in points] 651 | 652 | # Generate a manhattanized list of waypoints on the route while minimizing the number of required bends 653 | manh_point_list = self.manhattanize_point_list(initial_direction=current_dir, 654 | initial_point=current_point, 655 | points=points) 656 | 657 | for i in range(len(manh_point_list)): 658 | point = manh_point_list[i] 659 | if tuple(point[0]) not in self.route_point_dict: 660 | if i != 0: 661 | self.route_point_dict[tuple(point[0])] = self.route_point_dict[tuple(manh_point_list[i - 1][0])] 662 | 663 | # Simplify the point list so that each point corresponds with a bend of the route, i.e. no co-linear points 664 | # final_point_list = manh_point_list[1:] # Ignore the first pt, since it is co-incident with the starting port 665 | final_point_list = manh_point_list # Ignore the first pt, since it is co-incident with the starting port 666 | 667 | # print('---In cardinal router, manh_point_list:') 668 | # print(manh_point_list) 669 | # print('---In cardinal router, route_point_dict:') 670 | # print(self.route_point_dict) 671 | # print('---In cardinal router, final_point_list:') 672 | # print(final_point_list) 673 | 674 | # Draw a series of L-routes to follow the final simplified point list 675 | # for pt0, pt1 in zip(final_point_list, final_point_list[1:]): 676 | # print(f'drawing route {pt0[0]} -> {pt1[0]} on layer {pt0[1]}') 677 | for index_point in range(1, len(final_point_list) - 1): 678 | # print(' ') 679 | # print('Drawing route with width from %s to %s and turn towards %s' 680 | # % (final_point_list[index_point - 1], final_point_list[index_point], 681 | # final_point_list[index_point + 1])) 682 | self._draw_route_segment(pt0=final_point_list[index_point], 683 | pt1=final_point_list[index_point + 1], 684 | in_width=self.route_point_dict[tuple(final_point_list[index_point - 1][0])], 685 | out_width=self.route_point_dict[tuple(final_point_list[index_point][0])], 686 | enc_style=enc_style, 687 | prim=prim) 688 | 689 | # The loop does not draw the final straight segment, so add it here 690 | # print(' ') 691 | # print('Drawing route with width from %s to %s and stop' 692 | # % (final_point_list[index_point - 1], final_point_list[index_point])) 693 | self._draw_route_segment(pt0=final_point_list[-1], 694 | pt1=None, 695 | in_width=self.route_point_dict[tuple(final_point_list[-1][0])], 696 | out_width=self.route_point_dict[tuple(final_point_list[-1][0])], 697 | enc_style='uniform', 698 | prim=prim) 699 | 700 | # Clear instance variables for future routes 701 | if clear_route: 702 | self.route_points = [] 703 | self.route_point_dict = {} 704 | 705 | def _draw_route_segment(self, 706 | pt0: Tuple[Union[Tuple[float, float], XY], str], 707 | pt1: Optional[Tuple[Union[Tuple[float, float], XY], str]], 708 | enc_style: str = 'uniform', 709 | in_width: Optional[float] = None, 710 | out_width: Optional[float] = None, 711 | enc_bot: Optional[List[float]] = None, 712 | enc_top: Optional[List[float]] = None, 713 | prim: bool = True 714 | ) -> 'EZRouter': 715 | """ 716 | Draws a single straight route to pt0 then changes the direction of the route to be able to 717 | route to pt1 718 | 719 | Parameters 720 | ---------- 721 | pt0 : List[Tuple] 722 | First point in the route 723 | pt1 : Optional[List[Tuple]] 724 | Second point in the route 725 | enc_style : str 726 | 'uniform' to draw uniform enclosures, 'asymm' to draw min size asymmetric enclosures 727 | in_width : Optional[float] 728 | If provided, will change the first route segment to the desired width 729 | out_width : Optional[float] 730 | If provided, will set the second segment of the l-route to match this width, otherwise 731 | will maintain the same width 732 | enc_bot : Optional[List[float]] 733 | If provided, will use these enclosure settings for the bottom layer of the via 734 | enc_top : Optional[List[float]] 735 | If provided, will use these enclosure settings for the top layer of the via 736 | prim : bool 737 | True to use primitive vias 738 | 739 | Returns 740 | ------- 741 | self : AutoRouter 742 | Return self to make it easy to cascade connections 743 | """ 744 | if not self.current_rect or not self.current_handle or not self.current_dir: 745 | raise ValueError('Router has not been initialized, please call new_route()') 746 | 747 | # print('In _draw_route_segment, in_width = %f, out_width = %f' 748 | # % (in_width,out_width)) 749 | # print('In _draw_route_segment, pt0=%s, pt1=%s' % (str(pt0), str(pt1))) 750 | 751 | # Draw the first straight route segment 752 | self.draw_straight_route(loc=pt0[0], width=in_width) 753 | 754 | # Draw the via to turn the l-route 755 | # If an output width is not provided, use the same as the current width 756 | if not out_width: 757 | if self.current_dir == '+x' or self.current_dir == '-x': 758 | out_width = self.current_rect.get_dim('y') 759 | else: 760 | out_width = self.current_rect.get_dim('x') 761 | # Determine the output direction by checking the displacement to the next point 762 | # in the list 763 | if pt1: 764 | # TODO: Handle co-linear points properly here 765 | if self.current_dir == '+x' or self.current_dir == '-x': 766 | if self.current_rect[self.current_handle].y < XY(pt1[0]).y: 767 | direction = '+y' 768 | elif self.current_rect[self.current_handle].y == XY(pt1[0]).y and self.current_rect[self.current_handle].x < XY(pt1[0]).x: 769 | direction = '+x' 770 | elif self.current_rect[self.current_handle].y == XY(pt1[0]).y: 771 | direction = '-x' 772 | else: 773 | direction = '-y' 774 | else: 775 | if self.current_rect[self.current_handle].x < XY(pt1[0]).x: 776 | direction = '+x' 777 | elif self.current_rect[self.current_handle].x == XY(pt1[0]).x and self.current_rect[self.current_handle].y < XY(pt1[0]).y: 778 | direction = '+y' 779 | elif self.current_rect[self.current_handle].x == XY(pt1[0]).x: 780 | direction = '-y' 781 | else: 782 | direction = '-x' 783 | # If no next point is provided because it is at the end of the route, just use the 784 | # current direction. 785 | # TODO: Figure out if this is really the best way to go... 786 | else: 787 | direction = self.current_dir 788 | self.draw_via(layer=pt0[1], 789 | direction=direction, 790 | enc_style=enc_style, 791 | out_width=out_width, 792 | enc_top=enc_top, 793 | enc_bot=enc_bot, 794 | prim=prim) 795 | return self 796 | 797 | @staticmethod 798 | def manhattanize_point_list(initial_direction: str, 799 | initial_point: Tuple[Tuple[float, float], str], 800 | points: List[Tuple[Tuple[float, float], str]] 801 | ) -> List[Tuple[Tuple[float, float], str]]: 802 | """ 803 | Manhattanizes a provided list of (x, y) points while minimizing the number of times the direction changes. 804 | Manhattanization ensures that every segment of the route only traverses either the x or y direction. 805 | 806 | Notes 807 | ----- 808 | * Turn minimization is achieved in the following way: If the current direction is x, then the next point in 809 | the list will have dy = 0. If the current direction is y, then the next point in the list will have dx = 0 810 | 811 | Parameters 812 | ---------- 813 | initial_direction : str 814 | The current routing direction which must be maintained in the first segment 815 | initial_point : Tuple[Tuple[float, float], str] 816 | (x, y) coordinate location where the route will begin 817 | points : List[Tuple[Tuple[float, float], str]] 818 | List of coordinates which must also exist in the final manhattanized list 819 | 820 | Returns 821 | ------- 822 | manh_point_list : List[Tuple[float, float]] 823 | A manhattanized point list 824 | """ 825 | if initial_direction == '+x' or initial_direction == '-x': 826 | current_dir = 'x' 827 | else: 828 | current_dir = 'y' 829 | manh_point_list = [initial_point] 830 | current_point = initial_point 831 | # Iteratively generate a manhattan point list from the user provided point list 832 | for next_point in points: 833 | dx, dy = (next_point[0][0] - current_point[0][0]), (next_point[0][1] - current_point[0][1]) 834 | # If the upcoming point has a relative offset in both dimensions 835 | if dx != 0 and dy != 0: 836 | # Add an intermediate point 837 | if current_dir == 'x': 838 | # First move in x direction then y 839 | manh_point_list.append(((current_point[0][0] + dx, current_point[0][1]), current_point[1])) 840 | manh_point_list.append(next_point) 841 | current_point = manh_point_list[-1] 842 | current_dir = 'y' 843 | else: 844 | # First move in y direction then x 845 | manh_point_list.append(((current_point[0][0], current_point[0][1] + dy), current_point[1])) 846 | manh_point_list.append(next_point) 847 | current_point = manh_point_list[-1] 848 | current_dir = 'x' 849 | # If the point does not move ignore it to avoid adding co-linear points 850 | elif dx == 0 and dy == 0 and next_point[1] == current_point[1]: 851 | continue 852 | # If the next point only changes in one direction and it is not co-linear 853 | else: 854 | manh_point_list.append(next_point) 855 | current_point = manh_point_list[-1] 856 | if dx == 0: 857 | current_dir = 'y' 858 | else: 859 | current_dir = 'x' 860 | 861 | # Remove any co-linear points that are on the same metal layer 862 | del_idx = [] 863 | for i in range(len(manh_point_list) - 2): 864 | pt0 = manh_point_list[i] 865 | pt1 = manh_point_list[i + 1] 866 | pt2 = manh_point_list[i + 2] 867 | 868 | if pt0[0][0] == pt1[0][0] == pt2[0][0] and (pt0[0][1] <= pt1[0][1] <= pt2[0][1] or pt0[0][1] >= pt1[0][1] 869 | >= pt2[0][1]) and pt0[1] == pt1[1] == pt2[1]: 870 | del_idx.append(i + 1) 871 | elif pt0[0][1] == pt1[0][1] == pt2[0][1] and (pt0[0][0] <= pt1[0][0] <= pt2[0][0] or pt0[0][0] >= pt1[0][0] 872 | >= pt2[0][0]) and pt0[1] == pt1[1] == pt2[1]: 873 | del_idx.append(i + 1) 874 | 875 | return [manh_point_list[i] for i in range(len(manh_point_list)) if i not in del_idx] 876 | 877 | def add_relative_route_point(self, 878 | ref_rect: Rectangle, 879 | ref_handle: str, 880 | layer: str, 881 | width: float, 882 | offset: Tuple[float, float] = None, 883 | ): 884 | """ 885 | Adds a point to the routing path relative to some given rectangle and optional offset 886 | """ 887 | if len(ref_handle) == 1 and ref_handle != 'c': 888 | raise ValueError("Edge handles are invalid reference handles") 889 | temp_point = ref_rect.loc[ref_handle] 890 | offset_point = XY([temp_point.x + offset[0], temp_point.y + offset[1]]) 891 | self.route_points.append((offset_point, layer)) 892 | self.route_point_dict[offset_point] = width 893 | 894 | 895 | ''' Old Routing Methods to be Deprecated ''' 896 | 897 | def stretch_l_route(self, 898 | start_rect: Rectangle, 899 | start_dir: str, 900 | end_rect: Rectangle, 901 | via_size: Tuple[int, int] = (None, None) 902 | ): 903 | """ 904 | This method takes a starting rectangle and an ending rectangle and automatically routes an L between them 905 | 906 | Parameters 907 | ---------- 908 | start_rect : Rectangle 909 | The location where the route will be started 910 | start_dir : str 911 | 'x' or 'y' for the direction the first segment in the l route will traverse 912 | end_rect : Rectangle 913 | The location where the route will be ended 914 | via_size : Tuple[int, int] 915 | Overrides the array size of the via to be placed. 916 | 917 | Returns 918 | ------- 919 | route : Route 920 | The created route object containing all of the rects and vias 921 | """ 922 | if via_size != (None, None): 923 | print('WARNING, explicit via size is not yet supported') 924 | 925 | rect1 = self.gen.copy_rect(start_rect) 926 | rect2 = self.gen.copy_rect(end_rect) 927 | if start_dir == 'y': 928 | if rect2['t'] > rect1['t']: 929 | rect1.stretch('t', ref_rect=rect2, ref_handle='t') 930 | if rect2['c'].x > rect1['c'].x: 931 | rect2.stretch('l', ref_rect=rect1, ref_handle='l') 932 | else: 933 | rect2.stretch('r', ref_rect=rect1, ref_handle='r') 934 | else: 935 | rect1.stretch('b', ref_rect=rect2, ref_handle='b') 936 | if rect2['c'].x > rect1['c'].x: 937 | rect2.stretch('l', ref_rect=rect1, ref_handle='l') 938 | else: 939 | rect2.stretch('r', ref_rect=rect1, ref_handle='r') 940 | else: 941 | if rect2['r'] > rect1['r']: 942 | rect1.stretch('r', ref_rect=rect2, ref_handle='r') 943 | if rect2['c'].y > rect1['c'].y: 944 | rect2.stretch('b', ref_rect=rect1, ref_handle='b') 945 | else: 946 | rect2.stretch('t', ref_rect=rect1, ref_handle='t') 947 | else: 948 | rect1.stretch('l', ref_rect=rect2, ref_handle='l') 949 | if rect2['c'].y > rect1['c'].y: 950 | rect2.stretch('b', ref_rect=rect1, ref_handle='b') 951 | else: 952 | rect2.stretch('t', ref_rect=rect1, ref_handle='t') 953 | self.gen.connect_wires(rect1=rect1, rect2=rect2, size=via_size) 954 | 955 | def _set_handle_from_dir(self, direction: str) -> None: 956 | """ Determines the current rectangle handle based on the provided routing direction """ 957 | if direction == '+x': 958 | self.current_handle = 'cr' 959 | elif direction == '-x': 960 | self.current_handle = 'cl' 961 | elif direction == '+y': 962 | self.current_handle = 'ct' 963 | elif direction == '-y': 964 | self.current_handle = 'cb' 965 | --------------------------------------------------------------------------------