├── tests ├── __init__.py └── layout │ ├── __init__.py │ └── routing │ └── __init__.py ├── docs ├── .gitignore ├── source │ ├── api │ │ ├── modules.rst │ │ ├── bag.tech.rst │ │ ├── bag.mdao.rst │ │ ├── bag.math.rst │ │ ├── bag.design.rst │ │ ├── bag.rst │ │ ├── bag.layout.routing.rst │ │ ├── bag.util.rst │ │ ├── bag.io.rst │ │ ├── bag.verification.rst │ │ ├── bag.data.rst │ │ ├── bag.interface.rst │ │ └── bag.layout.rst │ ├── developer │ │ └── developer.rst │ ├── setup │ │ ├── pyoptsparse.rst │ │ ├── tech_config │ │ │ ├── layout │ │ │ │ └── layout.rst │ │ │ ├── misc.rst │ │ │ ├── tech_config.rst │ │ │ └── mos │ │ │ │ └── mos.rst │ │ ├── bag_config │ │ │ ├── bag_config.rst │ │ │ ├── misc.rst │ │ │ ├── socket │ │ │ │ └── socket.rst │ │ │ ├── simulation │ │ │ │ └── simulation.rst │ │ │ └── database │ │ │ │ └── database.rst │ │ ├── setup.rst │ │ ├── new_pdk.rst │ │ ├── config_summary.rst │ │ └── install_python.rst │ ├── overview │ │ ├── figures │ │ │ ├── bag_flow.png │ │ │ ├── gm_schematic.png │ │ │ ├── bag_prim_screenshot.png │ │ │ └── inv_chain_schematic.png │ │ ├── overview.rst │ │ ├── schematic.rst │ │ ├── testbench.rst │ │ └── design.rst │ ├── tutorial │ │ ├── figures │ │ │ ├── tran_prop.png │ │ │ ├── gm_schematic.png │ │ │ ├── gm_testbench.png │ │ │ ├── testbench_dut.png │ │ │ ├── bag_sim_server.png │ │ │ ├── tran_schematic.png │ │ │ └── bag_server_start.png │ │ ├── tutorial.rst │ │ └── collaboration.rst │ └── index.rst ├── refresh_api.sh ├── README └── Makefile ├── .gitignore ├── bag ├── util │ ├── __init__.py │ └── parse.py ├── mdao │ ├── __init__.py │ ├── components.py │ └── core.py ├── concurrent │ └── __init__.py ├── tech │ └── __init__.py ├── simulation │ └── __init__.py ├── layout │ ├── routing │ │ └── __init__.py │ └── __init__.py ├── interface │ ├── __init__.py │ ├── templates │ │ ├── calibreview_setup.txt │ │ ├── PrimModule.pyi │ │ ├── Module.pyi │ │ ├── load_results.ocn │ │ └── run_simulation.ocn │ ├── base.py │ ├── ocean.py │ ├── simulator.py │ ├── server.py │ └── zmqwrapper.py ├── design │ └── __init__.py ├── data │ ├── __init__.py │ ├── mos.py │ ├── digital.py │ └── dc.py ├── __init__.py ├── io │ ├── template.py │ ├── __init__.py │ ├── common.py │ ├── file.py │ ├── gui.py │ └── sim_data.py ├── verification │ ├── templates │ │ ├── si_env.txt │ │ └── layout_export_config.txt │ ├── __init__.py │ └── virtuoso.py ├── math │ └── __init__.py └── virtuoso.py ├── run_scripts ├── start_bag.sh ├── virt_server.sh ├── compile_verilog.il ├── generate_verilog.py └── setup_submodules.py ├── README.md ├── setup.py └── LICENSE /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /tests/layout/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/layout/routing/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | .idea 4 | build 5 | dist 6 | bag.egg-info 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /docs/source/api/modules.rst: -------------------------------------------------------------------------------- 1 | bag 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | bag 8 | -------------------------------------------------------------------------------- /docs/refresh_api.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tcsh 2 | 3 | sphinx-apidoc --force --output-dir=source/api ../bag 4 | -------------------------------------------------------------------------------- /docs/source/developer/developer.rst: -------------------------------------------------------------------------------- 1 | Developer Guide 2 | =============== 3 | 4 | Nothing here yet... 5 | -------------------------------------------------------------------------------- /docs/source/setup/pyoptsparse.rst: -------------------------------------------------------------------------------- 1 | Building Pyoptsparse 2 | ==================== 3 | 4 | To be written. 5 | -------------------------------------------------------------------------------- /bag/util/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package defines various utilities classes. 4 | """ -------------------------------------------------------------------------------- /bag/mdao/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package contains various openmdao related modules. 4 | """ -------------------------------------------------------------------------------- /bag/concurrent/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package define helper classes used to perform concurrent operations. 4 | """ -------------------------------------------------------------------------------- /docs/source/overview/figures/bag_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/overview/figures/bag_flow.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/tran_prop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/tran_prop.png -------------------------------------------------------------------------------- /docs/source/overview/figures/gm_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/overview/figures/gm_schematic.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/gm_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/gm_schematic.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/gm_testbench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/gm_testbench.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/testbench_dut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/testbench_dut.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/bag_sim_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/bag_sim_server.png -------------------------------------------------------------------------------- /docs/source/tutorial/figures/tran_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/tran_schematic.png -------------------------------------------------------------------------------- /bag/tech/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package contains various technology related utilities, such as transistor characterization. 4 | """ -------------------------------------------------------------------------------- /docs/source/tutorial/figures/bag_server_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/tutorial/figures/bag_server_start.png -------------------------------------------------------------------------------- /bag/simulation/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package defines various utility classes for running simulations and data post-processing. 4 | """ -------------------------------------------------------------------------------- /docs/source/overview/figures/bag_prim_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/overview/figures/bag_prim_screenshot.png -------------------------------------------------------------------------------- /docs/source/overview/figures/inv_chain_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardcwang/BAG_framework/HEAD/docs/source/overview/figures/inv_chain_schematic.png -------------------------------------------------------------------------------- /run_scripts/start_bag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PYTHONPATH="" 4 | 5 | # disable QT session manager warnings 6 | unset SESSION_MANAGER 7 | 8 | exec ${BAG_PYTHON} -m IPython 9 | -------------------------------------------------------------------------------- /bag/layout/routing/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package provide routing classes. 4 | """ 5 | 6 | from .base import TrackID, WireArray, Port, TrackManager 7 | from .grid import RoutingGrid 8 | from .fill import UsedTracks 9 | -------------------------------------------------------------------------------- /docs/README: -------------------------------------------------------------------------------- 1 | To build/update documentation: 2 | 3 | 1. make sure BAG Python's bin folder is in your path. 4 | 5 | 2. run: 6 | 7 | ./refresh_api.sh 8 | 9 | to generate API documentations. 10 | 11 | 3. run: 12 | 13 | make html 14 | 15 | to build the documentation webpage. 16 | -------------------------------------------------------------------------------- /docs/source/setup/tech_config/layout/layout.rst: -------------------------------------------------------------------------------- 1 | layout 2 | ====== 3 | 4 | This entry defines all layout specific settings. 5 | 6 | 7 | layout.em_temp 8 | -------------- 9 | 10 | The temperature used to calculate electro-migration specs. The temperature should 11 | be specified in degrees Celsius. 12 | -------------------------------------------------------------------------------- /bag/interface/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This packages defines classes to interface with CAD database and circuit simulators. 4 | """ 5 | 6 | from .server import SkillServer 7 | from .zmqwrapper import ZMQRouter, ZMQDealer 8 | 9 | __all__ = ['SkillServer', 'ZMQRouter', 'ZMQDealer', ] 10 | -------------------------------------------------------------------------------- /docs/source/setup/tech_config/misc.rst: -------------------------------------------------------------------------------- 1 | .. _tech_config_path: 2 | 3 | class 4 | ===== 5 | 6 | The subclass of :class:`bag.layout.core.TechInfo` for this process technology. 7 | If this entry is not defined, a default dummy :class:`~bag.layout.core.TechInfo` 8 | instance will be created for schematic-only design flow. 9 | -------------------------------------------------------------------------------- /bag/design/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package defines design template classes. 4 | """ 5 | 6 | from .module import Module, ModuleDB, SchInstance, MosModuleBase, ResPhysicalModuleBase, ResMetalModule 7 | 8 | __all__ = ['Module', 'ModuleDB', 'SchInstance', 'MosModuleBase', 'ResPhysicalModuleBase', 'ResMetalModule'] 9 | -------------------------------------------------------------------------------- /run_scripts/virt_server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export PYTHONPATH="${BAG_FRAMEWORK}" 4 | 5 | export cmd="-m bag.virtuoso run_skill_server" 6 | export min_port=5000 7 | export max_port=9999 8 | export port_file="BAG_server_port.txt" 9 | export log="skill_server.log" 10 | 11 | export cmd="${BAG_PYTHON} ${cmd} ${min_port} ${max_port} ${port_file} ${log}" 12 | exec $cmd 13 | -------------------------------------------------------------------------------- /bag/data/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package defines methods and classes useful for data post-processing. 4 | """ 5 | 6 | # compatibility import. 7 | from ..io import load_sim_results, save_sim_results, load_sim_file 8 | from .core import Waveform 9 | 10 | __all__ = ['load_sim_results', 'save_sim_results', 'load_sim_file', 11 | 'Waveform', ] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Berkeley Analog Generator (BAG) version 2.0 and later. 2 | 3 | BAG 2.0 is a complete rewrite of BAG 1.x (which is in pre-alpha stage and 4 | never released publicly). 5 | 6 | (Very outdated) Documentation and install instructions can be found at 7 | 8 | A tutorial setup is available at 9 | -------------------------------------------------------------------------------- /bag/layout/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package contains code for templated based layout. 4 | """ 5 | 6 | from .core import BagLayout, TechInfo 7 | from .routing import RoutingGrid 8 | from .template import TemplateDB 9 | from . import util 10 | 11 | 12 | __all__ = ['BagLayout', 'TechInfo', 13 | 'RoutingGrid', 14 | 'TemplateDB', 15 | ] 16 | -------------------------------------------------------------------------------- /docs/source/setup/tech_config/tech_config.rst: -------------------------------------------------------------------------------- 1 | Technology Configuration File 2 | ============================= 3 | 4 | Technology configuration file is written in YAML format. This document describes each setting. 5 | Technology configuration file may use environment variable to specify values of any entries. 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | misc 11 | mos/mos 12 | layout/layout 13 | -------------------------------------------------------------------------------- /docs/source/setup/bag_config/bag_config.rst: -------------------------------------------------------------------------------- 1 | BAG Configuration File 2 | ====================== 3 | 4 | BAG configuration file is written in YAML format. This document describes each setting. 5 | BAG configuration file may use environment variable to specify values of any entries. 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | socket/socket 11 | database/database 12 | simulation/simulation 13 | misc 14 | -------------------------------------------------------------------------------- /docs/source/setup/tech_config/mos/mos.rst: -------------------------------------------------------------------------------- 1 | mos 2 | === 3 | 4 | This entry defines all MOS transistor settings. 5 | 6 | 7 | mos.width_resolution 8 | -------------------- 9 | 10 | The transistor width minimum resolution, in meters or number of fins in finfet technology. 11 | 12 | mos.length_resolution 13 | --------------------- 14 | 15 | The transistor length minimum resolution, in meters. 16 | 17 | mos.mos_char_root 18 | ----------------- 19 | 20 | The default transistor characterization data directory. 21 | -------------------------------------------------------------------------------- /docs/source/setup/setup.rst: -------------------------------------------------------------------------------- 1 | BAG Setup Procedure 2 | =================== 3 | 4 | This document describes how to install Python for BAG and the various configuration settings. Since a lot of the 5 | configuration depends on the external CAD program and simulator, this document assumes you are using Virtuoso and 6 | Ocean (with ADEXL) for schematic design and simulation, respectively. 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | install_python 12 | pyoptsparse 13 | config_summary 14 | bag_config/bag_config 15 | tech_config/tech_config 16 | new_pdk 17 | -------------------------------------------------------------------------------- /docs/source/api/bag.tech.rst: -------------------------------------------------------------------------------- 1 | bag.tech package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.tech.core module 8 | -------------------- 9 | 10 | .. automodule:: bag.tech.core 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.tech.mos module 16 | ------------------- 17 | 18 | .. automodule:: bag.tech.mos 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: bag.tech 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/source/api/bag.mdao.rst: -------------------------------------------------------------------------------- 1 | bag.mdao package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.mdao.components module 8 | -------------------------- 9 | 10 | .. automodule:: bag.mdao.components 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.mdao.core module 16 | -------------------- 17 | 18 | .. automodule:: bag.mdao.core 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: bag.mdao 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/source/api/bag.math.rst: -------------------------------------------------------------------------------- 1 | bag.math package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.math.dfun module 8 | -------------------- 9 | 10 | .. automodule:: bag.math.dfun 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.math.interpolate module 16 | --------------------------- 17 | 18 | .. automodule:: bag.math.interpolate 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: bag.math 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/source/api/bag.design.rst: -------------------------------------------------------------------------------- 1 | bag.design package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.design.database module 8 | -------------------------- 9 | 10 | .. automodule:: bag.design.database 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.design.module module 16 | ------------------------ 17 | 18 | .. automodule:: bag.design.module 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: bag.design 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. BAG documentation master file, created by 2 | sphinx-quickstart on Fri May 27 15:45:44 2016. 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 BAG's documentation! 7 | =============================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | tutorial/tutorial 15 | overview/overview 16 | setup/setup 17 | developer/developer 18 | api/modules 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | 27 | -------------------------------------------------------------------------------- /bag/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This is the bag root package. 4 | """ 5 | 6 | import signal 7 | 8 | from . import math 9 | from .math import float_to_si_string, si_string_to_float 10 | from . import interface 11 | from . import design 12 | from . import data 13 | from . import tech 14 | from . import layout 15 | 16 | from .core import BagProject, create_tech_info 17 | 18 | __all__ = ['interface', 'design', 'data', 'math', 'tech', 'layout', 'BagProject', 19 | 'float_to_si_string', 'si_string_to_float', 'create_tech_info'] 20 | 21 | # make sure that SIGINT will always be catched by python. 22 | signal.signal(signal.SIGINT, signal.default_int_handler) 23 | -------------------------------------------------------------------------------- /docs/source/tutorial/tutorial.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | This section contains several simple tutorials for you to get an idea of the BAG workflow. 5 | 6 | In these tutorials, we will be using :program:`git` extensively. git allows you to copy a working setup, 7 | and it also allows you to checkout and use other people's design while they can work on adding future 8 | improvements. To learn git, you can read the documentations here_, or alternatively you can just 9 | google git commands to learn more about it while working through the tutorial. 10 | 11 | .. _here: https://git-scm.com/doc 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | schematic 17 | collaboration -------------------------------------------------------------------------------- /bag/io/template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines methods to create files from templates. 4 | """ 5 | 6 | from jinja2 import Environment, PackageLoader, select_autoescape 7 | 8 | 9 | def new_template_env(parent_package, tmp_folder): 10 | # type: (str, str) -> Environment 11 | return Environment(trim_blocks=True, 12 | lstrip_blocks=True, 13 | keep_trailing_newline=True, 14 | autoescape=select_autoescape(default_for_string=False), 15 | loader=PackageLoader(parent_package, package_path=tmp_folder), 16 | enable_async=False, 17 | ) 18 | -------------------------------------------------------------------------------- /bag/interface/templates/calibreview_setup.txt: -------------------------------------------------------------------------------- 1 | calibre_view_netlist_file : {{ netlist_file }} 2 | output_library : {{ lib_name }} 3 | schematic_library : {{ lib_name }} 4 | cell_name : {{ cell_name }} 5 | cellmap_file : {{ calibre_cellmap }} 6 | calibreview_log_file : ./calview.log 7 | calibreview_name : {{ view_name }} 8 | calibreview_type : schematic 9 | create_terminals : if_matching 10 | preserve_device_case : on 11 | execute_callbacks : off 12 | reset_properties : (m=1) 13 | magnify_devices_by : 1 14 | magnify_parasitics_by : 1 15 | device_placement : arrayed 16 | parasitic_placement : arrayed 17 | show_parasitic_polygons : off 18 | open_calibreview : don't_open 19 | generate_spectre_netlist : off 20 | -------------------------------------------------------------------------------- /bag/interface/templates/PrimModule.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import pkg_resources 5 | 6 | from bag.design.module import {{ module_name }} 7 | 8 | 9 | # noinspection PyPep8Naming 10 | class {{ lib_name }}__{{ cell_name }}({{ module_name }}): 11 | """design module for {{ lib_name }}__{{ cell_name }}. 12 | """ 13 | 14 | yaml_file = pkg_resources.resource_filename(__name__, 15 | os.path.join('netlist_info', 16 | '{{ cell_name }}.yaml')) 17 | 18 | def __init__(self, database, parent=None, prj=None, **kwargs): 19 | {{ module_name }}.__init__(self, database, self.yaml_file, parent=parent, prj=prj, **kwargs) 20 | -------------------------------------------------------------------------------- /bag/verification/templates/si_env.txt: -------------------------------------------------------------------------------- 1 | simStopList = '("auCdl") 2 | simViewList = '("auCdl" "schematic") 3 | globalGndSig = "" 4 | globalPowerSig = "" 5 | shrinkFACTOR = 0 6 | checkScale = "meter" 7 | diodeCheck = "none" 8 | capacitorCheck = "none" 9 | resistorCheck = "none" 10 | resistorModel = "" 11 | shortRES = 2000 12 | simNetlistHier = 't 13 | pinMAP = 'nil 14 | displayPININFO = 't 15 | checkLDD = 'nil 16 | connects = "" 17 | setEQUIV = "" 18 | simRunDir = "{{run_dir}}" 19 | hnlNetlistFileName = "{{output_name}}" 20 | simSimulator = "auCdl" 21 | simViewName = "{{view_name}}" 22 | simCellName = "{{cell_name}}" 23 | simLibName = "{{lib_name}}" 24 | incFILE = "{{source_added_file}}" 25 | cdlSimViewList = '("auCdl" "schematic") 26 | cdlSimStopList = '("auCdl") 27 | 28 | -------------------------------------------------------------------------------- /run_scripts/compile_verilog.il: -------------------------------------------------------------------------------- 1 | 2 | procedure( compile_netlist_views(fname "t") 3 | let( (p line info_list lib cell view obj cv) 4 | unless( p = infile(fname) 5 | error("Cannot open file %s" fname) 6 | ) 7 | while( gets(line p) 8 | info_list = parseString(line) 9 | lib = car(info_list) 10 | cell = cadr(info_list) 11 | view = caddr(info_list) 12 | obj = ddGetObj(lib cell view "verilog.sv" nil "a") 13 | cv = dbOpenCellViewByType(lib cell view "netlist" "ac") 14 | dbSetConnCurrent(cv) 15 | dbSave(cv) 16 | dbClose(cv) 17 | ) 18 | close(p) 19 | ) 20 | ) 21 | 22 | compile_netlist_views("verilog_cell_list.txt") 23 | -------------------------------------------------------------------------------- /bag/interface/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines the base of all interface classes. 4 | """ 5 | 6 | from typing import Dict, Any 7 | 8 | from ..io.template import new_template_env 9 | 10 | 11 | class InterfaceBase: 12 | """The base class of all interfaces. 13 | 14 | Provides various helper methods common to all interfaces. 15 | """ 16 | def __init__(self): 17 | self._tmp_env = new_template_env('bag.interface', 'templates') 18 | 19 | def render_file_template(self, temp_name, params): 20 | # type: (str, Dict[str, Any]) -> str 21 | """Returns the rendered content from the given template file.""" 22 | template = self._tmp_env.get_template(temp_name) 23 | return template.render(**params) 24 | -------------------------------------------------------------------------------- /docs/source/api/bag.rst: -------------------------------------------------------------------------------- 1 | bag package 2 | =========== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | bag.data 10 | bag.design 11 | bag.interface 12 | bag.io 13 | bag.layout 14 | bag.math 15 | bag.mdao 16 | bag.tech 17 | bag.util 18 | bag.verification 19 | 20 | Submodules 21 | ---------- 22 | 23 | bag.core module 24 | --------------- 25 | 26 | .. automodule:: bag.core 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.virtuoso module 32 | ------------------- 33 | 34 | .. automodule:: bag.virtuoso 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: bag 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/source/api/bag.layout.routing.rst: -------------------------------------------------------------------------------- 1 | bag.layout.routing package 2 | ========================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.layout.routing.base module 8 | ------------------------------ 9 | 10 | .. automodule:: bag.layout.routing.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.layout.routing.fill module 16 | ------------------------------ 17 | 18 | .. automodule:: bag.layout.routing.fill 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.layout.routing.grid module 24 | ------------------------------ 25 | 26 | .. automodule:: bag.layout.routing.grid 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: bag.layout.routing 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /bag/verification/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package contains LVS/RCX related verification methods. 4 | """ 5 | 6 | from typing import Any 7 | 8 | import importlib 9 | 10 | from .base import Checker 11 | 12 | __all__ = ['make_checker', 'Checker'] 13 | 14 | 15 | def make_checker(checker_cls, tmp_dir, **kwargs): 16 | # type: (str, str, **Any) -> Checker 17 | """Returns a checker object. 18 | 19 | Parameters 20 | ----------- 21 | checker_cls : str 22 | the Checker class absolute path name. 23 | tmp_dir : str 24 | directory to save temporary files in. 25 | **kwargs : Any 26 | keyword arguments needed to create a Checker object. 27 | """ 28 | sections = checker_cls.split('.') 29 | 30 | module_str = '.'.join(sections[:-1]) 31 | class_str = sections[-1] 32 | module = importlib.import_module(module_str) 33 | return getattr(module, class_str)(tmp_dir, **kwargs) 34 | -------------------------------------------------------------------------------- /docs/source/api/bag.util.rst: -------------------------------------------------------------------------------- 1 | bag.util package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.util.interval module 8 | ------------------------ 9 | 10 | .. automodule:: bag.util.interval 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.util.libimport module 16 | ------------------------- 17 | 18 | .. automodule:: bag.util.libimport 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.util.parse module 24 | --------------------- 25 | 26 | .. automodule:: bag.util.parse 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.util.search module 32 | ---------------------- 33 | 34 | .. automodule:: bag.util.search 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: bag.util 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /bag/io/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package provides all IO related functionalities for BAG. 4 | 5 | Most importantly, this module sorts out all the bytes v.s. unicode differences 6 | and simplifies writing python2/3 compatible code. 7 | """ 8 | 9 | from .common import fix_string, to_bytes, set_encoding, get_encoding, \ 10 | set_error_policy, get_error_policy 11 | from .sim_data import load_sim_results, save_sim_results, load_sim_file 12 | from .file import read_file, read_resource, read_yaml, readlines_iter, \ 13 | write_file, make_temp_dir, open_temp, open_file 14 | 15 | from . import process 16 | 17 | __all__ = ['fix_string', 'to_bytes', 'set_encoding', 'get_encoding', 18 | 'set_error_policy', 'get_error_policy', 19 | 'load_sim_results', 'save_sim_results', 'load_sim_file', 20 | 'read_file', 'read_resource', 'read_yaml', 'readlines_iter', 21 | 'write_file', 'make_temp_dir', 'open_temp', 'open_file', 22 | ] 23 | -------------------------------------------------------------------------------- /docs/source/api/bag.io.rst: -------------------------------------------------------------------------------- 1 | bag.io package 2 | ============== 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.io.common module 8 | -------------------- 9 | 10 | .. automodule:: bag.io.common 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.io.file module 16 | ------------------ 17 | 18 | .. automodule:: bag.io.file 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.io.gui module 24 | ----------------- 25 | 26 | .. automodule:: bag.io.gui 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.io.process module 32 | --------------------- 33 | 34 | .. automodule:: bag.io.process 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | bag.io.sim_data module 40 | ---------------------- 41 | 42 | .. automodule:: bag.io.sim_data 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: bag.io 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /docs/source/api/bag.verification.rst: -------------------------------------------------------------------------------- 1 | bag.verification package 2 | ======================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.verification.base module 8 | ---------------------------- 9 | 10 | .. automodule:: bag.verification.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.verification.calibre module 16 | ------------------------------- 17 | 18 | .. automodule:: bag.verification.calibre 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.verification.pvs module 24 | --------------------------- 25 | 26 | .. automodule:: bag.verification.pvs 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.verification.virtuoso_export module 32 | --------------------------------------- 33 | 34 | .. automodule:: bag.verification.virtuoso_export 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: bag.verification 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/source/setup/bag_config/misc.rst: -------------------------------------------------------------------------------- 1 | class 2 | ===== 3 | 4 | The subclass of :ref: 5 | 6 | 7 | .. _bag_lib_defs: 8 | 9 | lib_defs 10 | ======== 11 | 12 | Location of the BAG design module libraries definition file. 13 | 14 | The BAG libraries definition file is similar to the ``cds.lib`` file for Virtuoso, where it defines every design module 15 | library and its location. This file makes it easy to share design module libraries made by different designers. 16 | 17 | Each line in the file contains two entries, separated by spaces. The first entry is the name of the design module 18 | library, and the second entry is the location of the design module library. Environment variables may be used in this 19 | file. 20 | 21 | .. _bag_new_lib_path: 22 | 23 | new_lib_path 24 | ============ 25 | 26 | Directory to put new generated design module libraries. 27 | 28 | When you import a new schematic generator library, BAG will create a corresponding Python design module library and 29 | define this library in the library definition file (see :ref:`bag_lib_defs`). This field tells BAG where new design 30 | module libraries should be created. 31 | -------------------------------------------------------------------------------- /run_scripts/generate_verilog.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | import yaml 5 | from jinja2 import Environment, FileSystemLoader 6 | 7 | 8 | def run_main(): 9 | verilog_dir = 'verilog_models' 10 | cell_map_fname = 'verilog_cell_map.yaml' 11 | skill_read_fname = 'verilog_cell_list.txt' 12 | lib_name = 'AAAMODEL_QDR_HYBRID3' 13 | lib_loc = 'gen_libs' 14 | view_name = 'systemVerilog' 15 | model_fname = 'verilog.sv' 16 | 17 | with open(cell_map_fname, 'r') as f: 18 | cell_map = yaml.load(f) 19 | 20 | jinja_env = Environment(loader=FileSystemLoader(verilog_dir)) 21 | 22 | with open(skill_read_fname, 'w') as g: 23 | for cell_name, fname in cell_map.items(): 24 | root_dir = os.path.join(lib_loc, lib_name, cell_name, view_name) 25 | os.makedirs(root_dir, exist_ok=True) 26 | 27 | content = jinja_env.get_template(fname).render(cell_name=cell_name) 28 | 29 | with open(os.path.join(root_dir, model_fname), 'w') as f: 30 | f.write(content) 31 | 32 | g.write('%s %s %s\n' % (lib_name, cell_name, view_name)) 33 | 34 | 35 | if __name__ == '__main__': 36 | run_main() 37 | -------------------------------------------------------------------------------- /docs/source/setup/bag_config/socket/socket.rst: -------------------------------------------------------------------------------- 1 | socket 2 | ====== 3 | 4 | This entry defines socket settings for BAG to communicate with Virtuoso. 5 | 6 | socket.host 7 | ----------- 8 | 9 | The host of the BAG server socket, i.e. the machine running the Virtuoso program. usually ``localhost``. 10 | 11 | socket.port_file 12 | ---------------- 13 | 14 | File containing socket port number for BAG server. When Virtuoso starts the BAG server process, it finds a open port and bind the 15 | server to this port. It then creates a file with name in ``$BAG_WORK_DIR`` directory, and write the port number to this 16 | file. 17 | 18 | socket.sim_port_file 19 | -------------------- 20 | 21 | File containing socket port number for simulation server. When the simulation server starts, it finds a open port and bind the 22 | server to this port. It then creates a file with name in ``$BAG_WORK_DIR`` directory, and write the port number to this 23 | file. 24 | 25 | 26 | socket.log_file 27 | --------------- 28 | 29 | Socket communication debugging log file. All messages sent or received by BAG will be recorded in this log. 30 | 31 | socket.pipeline 32 | --------------- 33 | 34 | number of messages allowed in the ZMQ pipeline. Usually you don't have to change this. 35 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | setup( 7 | name='bag', 8 | version='2.0', 9 | description='Berkeley Analog Generator', 10 | classifiers=[ 11 | 'Development Status :: 3 - Alpha', 12 | 'License :: OSI Approved :: BSD License', 13 | 'Operating System :: POSIX :: Linux', 14 | 'Programming Language :: Python :: 3.5', 15 | 'Programming Language :: Python :: 3.6', 16 | 'Programming Language :: Python :: 3.7', 17 | ], 18 | author='Eric Chang', 19 | author_email='pkerichang@berkeley.edu', 20 | packages=find_packages(), 21 | python_requires='>=3.5', 22 | install_requires=[ 23 | 'setuptools>=18.5', 24 | 'PyYAML>=3.11', 25 | 'Jinja2>=2.9', 26 | 'numpy>=1.10', 27 | 'networkx>=1.11', 28 | 'pexpect>=4.0', 29 | 'pyzmq>=15.2.0', 30 | 'scipy>=0.17', 31 | 'matplotlib>=1.5', 32 | 'rtree', 33 | 'h5py', 34 | 'Shapely', 35 | ], 36 | extras_require={ 37 | 'mdao': ['openmdao'] 38 | }, 39 | tests_require=[ 40 | 'openmdao', 41 | 'pytest', 42 | ], 43 | package_data={ 44 | 'bag.interface': ['templates/*'], 45 | 'bag.verification': ['templates/*'], 46 | }, 47 | ) 48 | -------------------------------------------------------------------------------- /docs/source/api/bag.data.rst: -------------------------------------------------------------------------------- 1 | bag.data package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.data.core module 8 | -------------------- 9 | 10 | .. automodule:: bag.data.core 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.data.dc module 16 | ------------------ 17 | 18 | .. automodule:: bag.data.dc 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.data.digital module 24 | ----------------------- 25 | 26 | .. automodule:: bag.data.digital 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.data.lti module 32 | ------------------- 33 | 34 | .. automodule:: bag.data.lti 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | bag.data.ltv module 40 | ------------------- 41 | 42 | .. automodule:: bag.data.ltv 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | bag.data.mos module 48 | ------------------- 49 | 50 | .. automodule:: bag.data.mos 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | bag.data.plot module 56 | -------------------- 57 | 58 | .. automodule:: bag.data.plot 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | 64 | Module contents 65 | --------------- 66 | 67 | .. automodule:: bag.data 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | -------------------------------------------------------------------------------- /docs/source/api/bag.interface.rst: -------------------------------------------------------------------------------- 1 | bag.interface package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | bag.interface.database module 8 | ----------------------------- 9 | 10 | .. automodule:: bag.interface.database 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | bag.interface.ocean module 16 | -------------------------- 17 | 18 | .. automodule:: bag.interface.ocean 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | bag.interface.server module 24 | --------------------------- 25 | 26 | .. automodule:: bag.interface.server 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | bag.interface.simulator module 32 | ------------------------------ 33 | 34 | .. automodule:: bag.interface.simulator 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | bag.interface.skill module 40 | -------------------------- 41 | 42 | .. automodule:: bag.interface.skill 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | bag.interface.zmqwrapper module 48 | ------------------------------- 49 | 50 | .. automodule:: bag.interface.zmqwrapper 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: bag.interface 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /docs/source/setup/new_pdk.rst: -------------------------------------------------------------------------------- 1 | Setting up New PDK 2 | ================== 3 | 4 | This section describes how to get BAG 2.0 to work with a new PDK. 5 | 6 | #. Create a new technology configuration file for this PDK. See :doc:`tech_config/tech_config` for a description of 7 | the technology configuration file format. 8 | 9 | #. Create a new BAG configuration file for this PDK. You can simply copy an existing configuration, then change the 10 | fields listed in :ref:`change_pdk`. 11 | 12 | #. Create a new ``BAG_prim`` library for this PDK. The easiest way to do this is to copy an existing ``BAG_prim`` 13 | library, then change the underlying instances to be instances from the new PDK. You should use the **pPar** command 14 | in Virtuoso to pass CDF parameters from ``BAG_prim`` instances to PDK instances. 15 | 16 | #. Change your cds.lib to refer to the new ``BAG_prim`` library. 17 | 18 | #. To avoid everyone having their own python design modules for BAG primitive, you should generated a global design module 19 | library for BAG primitives, then ask every user to include this global library in their ``bag_libs.def`` file. To 20 | do so, setup a BAG workspace and execute the following commands: 21 | 22 | .. code-block:: python 23 | 24 | import bag 25 | prj = bag.BagProject() 26 | prj.import_design_library('BAG_prim') 27 | 28 | now copy the generate design library to a global location. 29 | -------------------------------------------------------------------------------- /docs/source/api/bag.layout.rst: -------------------------------------------------------------------------------- 1 | bag.layout package 2 | ================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | bag.layout.routing 10 | 11 | Submodules 12 | ---------- 13 | 14 | bag.layout.connection module 15 | ---------------------------- 16 | 17 | .. automodule:: bag.layout.connection 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | bag.layout.core module 23 | ---------------------- 24 | 25 | .. automodule:: bag.layout.core 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | bag.layout.digital module 31 | ------------------------- 32 | 33 | .. automodule:: bag.layout.digital 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | bag.layout.objects module 39 | ------------------------- 40 | 41 | .. automodule:: bag.layout.objects 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | bag.layout.template module 47 | -------------------------- 48 | 49 | .. automodule:: bag.layout.template 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | bag.layout.util module 55 | ---------------------- 56 | 57 | .. automodule:: bag.layout.util 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | 63 | Module contents 64 | --------------- 65 | 66 | .. automodule:: bag.layout 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | -------------------------------------------------------------------------------- /bag/util/parse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines parsing utility methods. 4 | """ 5 | 6 | import ast 7 | 8 | 9 | class ExprVarScanner(ast.NodeVisitor): 10 | """ 11 | This node visitor collects all variable names found in the 12 | AST, and excludes names of functions. Variables having 13 | dotted names are not supported. 14 | """ 15 | def __init__(self): 16 | self.varnames = set() 17 | 18 | # noinspection PyPep8Naming 19 | def visit_Name(self, node): 20 | self.varnames.add(node.id) 21 | 22 | # noinspection PyPep8Naming 23 | def visit_Call(self, node): 24 | if not isinstance(node.func, ast.Name): 25 | self.visit(node.func) 26 | for arg in node.args: 27 | self.visit(arg) 28 | 29 | # noinspection PyPep8Naming 30 | def visit_Attribute(self, node): 31 | # ignore attributes 32 | pass 33 | 34 | 35 | def get_variables(expr): 36 | """Parses the given Python expression and return a list of all variables. 37 | 38 | Parameters 39 | ---------- 40 | expr : str 41 | An expression string that we want to parse for variable names. 42 | 43 | Returns 44 | ------- 45 | var_list : list[str] 46 | Names of variables from the given expression. 47 | """ 48 | root = ast.parse(expr, mode='exec') 49 | scanner = ExprVarScanner() 50 | scanner.visit(root) 51 | return list(scanner.varnames) 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Regents of the University of California 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 | -------------------------------------------------------------------------------- /bag/interface/templates/Module.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from typing import Dict 4 | 5 | import os 6 | import pkg_resources 7 | 8 | from bag.design.module import Module 9 | 10 | 11 | # noinspection PyPep8Naming 12 | class {{ lib_name }}__{{ cell_name }}(Module): 13 | """Module for library {{ lib_name }} cell {{ cell_name }}. 14 | 15 | Fill in high level description here. 16 | """ 17 | yaml_file = pkg_resources.resource_filename(__name__, 18 | os.path.join('netlist_info', 19 | '{{ cell_name }}.yaml')) 20 | 21 | 22 | def __init__(self, database, parent=None, prj=None, **kwargs): 23 | Module.__init__(self, database, self.yaml_file, parent=parent, prj=prj, **kwargs) 24 | 25 | @classmethod 26 | def get_params_info(cls): 27 | # type: () -> Dict[str, str] 28 | """Returns a dictionary from parameter names to descriptions. 29 | 30 | Returns 31 | ------- 32 | param_info : Optional[Dict[str, str]] 33 | dictionary from parameter names to descriptions. 34 | """ 35 | return dict( 36 | ) 37 | 38 | def design(self): 39 | """To be overridden by subclasses to design this module. 40 | 41 | This method should fill in values for all parameters in 42 | self.parameters. To design instances of this module, you can 43 | call their design() method or any other ways you coded. 44 | 45 | To modify schematic structure, call: 46 | 47 | rename_pin() 48 | delete_instance() 49 | replace_instance_master() 50 | reconnect_instance_terminal() 51 | restore_instance() 52 | array_instance() 53 | """ 54 | pass 55 | -------------------------------------------------------------------------------- /bag/verification/templates/layout_export_config.txt: -------------------------------------------------------------------------------- 1 | case "preserve" 2 | cellListFile "" 3 | cellMap "" 4 | cellNamePrefix "" 5 | cellNameSuffix "" 6 | convertDot "node" 7 | convertPin "geometry" 8 | #doNotPreservePcellPins 9 | #flattenPcells 10 | #flattenVias 11 | fontMap "" 12 | #ignoreLines 13 | #ignorePcellEvalFail 14 | labelCase "preserve" 15 | labelDepth "1" 16 | labelMap "" 17 | layerMap "" 18 | library "{{lib_name}}" 19 | logFile "strmOut.log" 20 | maxVertices "200" 21 | #mergePathSegsToPath 22 | #noConvertHalfWidthPath 23 | noInfo "" 24 | #noObjectProp 25 | #noOutputTextDisplays 26 | #noOutputUnplacedInst 27 | noWarn "" 28 | objectMap "" 29 | outputDir "{{run_dir}}" 30 | #pathToPolygon 31 | pinAttNum "0" 32 | propMap "" 33 | #propValueOnly 34 | #rectToBox 35 | refLibList "" 36 | #replaceBusBitChar 37 | #reportPrecisionLoss 38 | #respectGDSIINameLimit 39 | runDir "{{run_dir}}" 40 | #snapToGrid 41 | strmFile "{{output_name}}" 42 | strmVersion "5" 43 | summaryFile "" 44 | techLib "" 45 | topCell "{{cell_name}}" 46 | userSkillFile "" 47 | viaMap "" 48 | view "{{view_name}}" 49 | warnToErr "" 50 | -------------------------------------------------------------------------------- /docs/source/setup/config_summary.rst: -------------------------------------------------------------------------------- 1 | Configuration Files Summary 2 | =========================== 3 | 4 | Although BAG has many configuration settings, most of them do not need to be changed. This file summarizes which 5 | settings you should modify under various use cases. 6 | 7 | Starting New Project 8 | -------------------- 9 | 10 | For every new project, it is a good practice to keep a set of global configuration files to make sure everyone working 11 | on the project is simulating the same corners, running LVS and extraction with the same settings, and so on. In this 12 | case, you should change the following fields to point to the global configuration files: 13 | 14 | * :ref:`sim_env_file` 15 | * :ref:`lvs_runset` 16 | * :ref:`rcx_runset` 17 | * :ref:`calibre_cellmap` 18 | 19 | Customizing Virtuoso Setups 20 | --------------------------- 21 | 22 | If you changed your Virtuoso setup (configuration files, working directory, etc.), double check the following fields to 23 | see if they need to be modified: 24 | 25 | * :ref:`lvs_rundir` 26 | * :ref:`rcx_rundir` 27 | * :ref:`sim_init_file` 28 | 29 | Python Design Module Customization 30 | ---------------------------------- 31 | 32 | The following fields control how BAG 2.0 finds design modules, and also where it puts new imported modules: 33 | 34 | * :ref:`bag_lib_defs` 35 | * :ref:`bag_new_lib_path` 36 | 37 | .. _change_pdk: 38 | 39 | Changing Process Technology 40 | --------------------------- 41 | 42 | If you want to change the process technology, double check the following fields: 43 | 44 | * :ref:`sch_tech_lib` 45 | * :ref:`sch_exclude` 46 | * :ref:`tb_config_libs` 47 | * :ref:`tech_config_path` 48 | 49 | The following fields probably won't change, but if something doesn't work it's worth to double check: 50 | 51 | * :ref:`sch_sympin` 52 | * :ref:`sch_ipin` 53 | * :ref:`sch_opin` 54 | * :ref:`sch_iopin` 55 | * :ref:`sch_simulators` 56 | 57 | -------------------------------------------------------------------------------- /docs/source/setup/bag_config/simulation/simulation.rst: -------------------------------------------------------------------------------- 1 | simulation 2 | ========== 3 | 4 | This entry defines all settings related to Ocean. 5 | 6 | simulation.class 7 | ---------------- 8 | 9 | The Python class that handles simulator interaction. This entry is mainly to support non-Ocean simulators. If you 10 | use Ocean, the value must be ``bag.interface.ocean.OceanInterface``. 11 | 12 | simulation.prompt 13 | ----------------- 14 | 15 | The ocean prompt string. 16 | 17 | .. _sim_init_file: 18 | 19 | simulation.init_file 20 | -------------------- 21 | 22 | This file will be loaded when Ocean first started up. This allows you to configure the Ocean simulator. If you do not want to load an initialization file, set this field to an empty string (``""``). 23 | 24 | simulation.view 25 | --------------- 26 | 27 | Testbench view name. Usually ``adexl``. 28 | 29 | simulation.state 30 | ---------------- 31 | 32 | ADE-XL setup state name. When you run simulations from BAG, the simulation configuration will be saved to this setup 33 | state. 34 | 35 | simulation.update_timeout_ms 36 | ---------------------------- 37 | 38 | If simulation takes a lone time, BAG will print out a message at this time interval (in milliseconds) so you can know 39 | if BAG is still running. 40 | 41 | simulation.kwargs 42 | ----------------- 43 | 44 | pexpect keyword arguments dictionary used to start the simulation. When BAG server receive a simulation request, it 45 | will run Ocean in a subprocess using Python pexpect module. This entry allows you to control how pexpect starts the 46 | Ocean subprocess. Refer to pexpect documentation for more information. 47 | 48 | job_options 49 | ----------- 50 | 51 | A dictionary of job options for ADE-XL. This entry controls whether ADE-XL runs simulations remotely or locally, and how many jobs it launches for a simulation run. Refer to ADE-XL documentation for available options. 52 | -------------------------------------------------------------------------------- /docs/source/overview/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | .. figure:: ./figures/bag_flow.png 5 | :align: center 6 | :figclass: align-center 7 | 8 | BAG design flow diagram 9 | 10 | BAG is a Python-based circuit design platform that aims to automate analog circuit design, but at the same time give the 11 | user full visibility and control over every step in the design flow. 12 | 13 | The analog circuit design flow is generally as follows: 14 | 15 | #. Create a schematic generator of the circuit. 16 | #. Create a testbench generator to measure specifications and verify functionality. 17 | #. Create a layout generator if post-extraction verification is needed. 18 | #. Generate a schematic with given specifications. 19 | #. Generate a testbench that instantiates the generated schematic. 20 | #. Simulate the testbenches and post-process data to verify that the circuit meets specifications. 21 | #. Create the layout of your schematic and verify it's LVS/DRC clean. 22 | #. Repeat step 3 on post-extraction schematic. 23 | 24 | BAG 2.0 is designed so that any or all steps of the design flow can be performed in a Python script or console, thus 25 | enabling rapid design iteration and architecture exploration. 26 | 27 | To achieve its goal, BAG is divided into 4 components: schematic generators, layout generators, design modules, and 28 | testbench generators. These components are independent from one another, so the designer can pick and choose which steps 29 | in the design flow to automate. For example, the designer can simply use BAG to generate new schematics, and use his 30 | own CAD program for simulation and verification. Alternatively, The designer can provide an existing schematic to BAG 31 | and simply use it to automate the verification process. 32 | 33 | BAG interacts with an external CAD program or simulator to complete all the design and simulation tasks. BAG comes with 34 | Virtuoso and Ocean simulator support, but can be extended to other CAD programs or simulators. The rest of this 35 | document assumes you are using Virtuoso and running simulations in Ocean. 36 | 37 | Next we will describe each components of BAG in detail. 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | 42 | schematic 43 | design 44 | testbench -------------------------------------------------------------------------------- /docs/source/overview/schematic.rst: -------------------------------------------------------------------------------- 1 | Schematic Generator 2 | =================== 3 | 4 | A schematic generator is a schematic in your CAD program that tells BAG all the information needed to create a design. 5 | BAG creates design modules from schematic generators, and BAG will copy and modify schematic generators to implement 6 | new designs. 7 | 8 | .. figure:: ./figures/gm_schematic.png 9 | :align: center 10 | :figclass: align-center 11 | 12 | An example schematic generator of a differential gm cell. 13 | 14 | A schematic generator needs to follow some rules to work with BAG: 15 | 16 | #. Instances in a schematic generator must be other schematic generators, or a cell in the ``BAG_prim`` library. 17 | #. BAG can array any instance in a schematic generator. That is, in the design implementation phase, BAG can 18 | copy/paste this instance any number of times, and modify the connections or parameters of any copy. This is useful 19 | in creating array structures, such as an inverter chain with variable number of stages, or a DAC with variable 20 | number of bits. 21 | 22 | However, if you need to array an instance, its ports must be connected to wire stubs, with net labels on each of the 23 | wire stubs. Also, there must be absolutely nothing to the right of the instance, since BAG will array the instance 24 | by copying-and-pasting to the right. An example of an inverter buffer chain schematic generator is shown below. 25 | 26 | .. figure:: ./figures/inv_chain_schematic.png 27 | :align: center 28 | :figclass: align-center 29 | 30 | An example schematic generator of an inverter buffer chain. Ports connected by wire stubs, nothing on the right. 31 | 32 | #. BAG can replace the instance master of any instance. The primary use of this is to allow the designer to change 33 | transistor threshold values, but this could be used for other schematic generators if implemented. Whenever you 34 | switch the instance master of an instance, the symbol of the new instance must exactly match the old instance, 35 | including the port names. 36 | #. Although not required, it is good practice to fill in default parameter values for all instances from the 37 | ``BAG_prim`` library. This makes it so that you can simulate a schematic generator in a normal testbench, and make 38 | debugging easier. 39 | 40 | -------------------------------------------------------------------------------- /docs/source/overview/testbench.rst: -------------------------------------------------------------------------------- 1 | Testbench Generator 2 | =================== 3 | 4 | A testbench generator is just a normal testbench with schematic and adexl view. BAG will simply copy the schematic and 5 | adexl view, and replace the device under test with the new generated schematic. There are only 3 restrictions to the 6 | testbench: 7 | 8 | #. All device-under-test's (DUTs) in the testbench must have an instance name starting with ``XDUT``. This is to inform BAG 9 | which child instances should be replaced. 10 | #. The testbench must be configured to simulate with ADE-XL. This is to make parametric/corner sweeps and monte carlo 11 | easier. 12 | #. You should not define any process corners in the ADE-XL state, as BAG will load them for you. This makes it 13 | possible to use the same testbench generator across different technologies. 14 | 15 | To verify a new design, call :meth:`~bag.BagProject.create_testbench` and specify the testbench generator library/cell, 16 | DUT library/cell, and the library to create the new testbench in. BAG will create a :class:`~bag.core.Testbench` object 17 | to represent this testbench. You can then call its methods to set the parameters, process corners, or enable parametric 18 | sweeps. When you're done, call :meth:`~bag.core.Testbench.update_testbench` to commit the changes to Virtuoso. If you 19 | do not wish to run simulation in BAG, you can then open this testbench in Virtuoso and simulate it there. 20 | 21 | If you want to start simulation from BAG and load simulation data, you need to call 22 | :meth:`~bag.core.Testbench.add_output` method to specify which outputs to record and send back to Python. Output 23 | expression is a Virtuoso calculator expression. Then, call :meth:`~bag.core.Testbench.run_simulation` to start a 24 | simulation run. During the simulation, you can press ``Ctrl-C`` anytime to abort simulation. When the simulation 25 | finish, the result directory will be saved to the attribute :attr:`~bag.core.Testbench.save_dir`, and you can call 26 | :func:`bag.data.load_sim_results` to load the result in Python. See :doc:`/tutorial/tutorial` for an example. 27 | 28 | Since BAG uses the ADE-XL interface to run simulation, all simulation runs will be recorded in ADE-XL's history tab, so 29 | you can plot them in Virtuoso later for debugging purposes. By default, all simulation runs from BAG has the ``BagSim`` 30 | history tag, but you can also specify your own tag name when you call :meth:`~bag.core.Testbench.run_simulation`. Read 31 | ADE-XL documentation if you want to know more about ADE-XL's history feature. 32 | -------------------------------------------------------------------------------- /bag/io/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module contains some commonly used IO functions. 4 | 5 | In particular, this module keeps track of BAG's system-wide encoding/decoding settings. 6 | """ 7 | 8 | # default BAG file encoding. 9 | bag_encoding = 'utf-8' 10 | # default codec error policy 11 | bag_codec_error = 'replace' 12 | 13 | 14 | def fix_string(obj): 15 | """Fix the given potential string object to ensure python 2/3 compatibility. 16 | 17 | If the given object is raw bytes, decode it into a string using 18 | current encoding and return it. Otherwise, just return the given object. 19 | 20 | This method is useful for writing python 2/3 compatible code. 21 | 22 | Parameters 23 | ---------- 24 | obj : 25 | any python object. 26 | 27 | Returns 28 | ------- 29 | val : 30 | the given object, or a decoded string if the given object is bytes. 31 | """ 32 | if isinstance(obj, bytes): 33 | obj = obj.decode(encoding=bag_encoding, errors=bag_codec_error) 34 | return obj 35 | 36 | 37 | def to_bytes(my_str): 38 | """Convert the given string to raw bytes. 39 | 40 | Parameters 41 | ---------- 42 | my_str : string 43 | the string to encode to bytes. 44 | 45 | Returns 46 | ------- 47 | val : bytes 48 | raw bytes of the string. 49 | """ 50 | return bytes(my_str.encode(encoding=bag_encoding, errors=bag_codec_error)) 51 | 52 | 53 | def set_encoding(new_encoding): 54 | """Sets the BAG input/output encoding. 55 | 56 | Parameters 57 | ---------- 58 | new_encoding : string 59 | the new encoding name. 60 | """ 61 | global bag_encoding 62 | if not isinstance(new_encoding, str): 63 | raise Exception('encoding name must be string/unicode.') 64 | bag_encoding = new_encoding 65 | 66 | 67 | def get_encoding(): 68 | """Returns the BAG input/output encoding. 69 | 70 | Returns 71 | ------- 72 | bag_encoding : unicode 73 | the encoding name. 74 | """ 75 | return bag_encoding 76 | 77 | 78 | def set_error_policy(new_policy): 79 | """Sets the error policy on encoding/decoding errors. 80 | 81 | Parameters 82 | ---------- 83 | new_policy : string 84 | the new error policy name. See codecs package documentation 85 | for more information. 86 | """ 87 | global bag_codec_error 88 | bag_codec_error = new_policy 89 | 90 | 91 | def get_error_policy(): 92 | """Returns the current BAG encoding/decoding error policy. 93 | 94 | Returns 95 | ------- 96 | policy : unicode 97 | the current error policy name. 98 | """ 99 | return bag_codec_error 100 | -------------------------------------------------------------------------------- /docs/source/setup/install_python.rst: -------------------------------------------------------------------------------- 1 | Installing Python for BAG 2 | ========================== 3 | 4 | This section describes how to install Python for running BAG. 5 | 6 | Installation Requirements 7 | ------------------------- 8 | 9 | BAG is compatible with Python 3.5+ (Python 2.7+ is theoretically supported but untested), so you will need to have 10 | Python 3.5+ installed. For Linux/Unix systems, it is recommended to install a separate Python distribution from 11 | the system Python. 12 | 13 | BAG requires multiple Python packages, some of which requires compiling C++/C/Fortran extensions. Therefore, it is 14 | strongly recommended to download `Anaconda Python `_, which provides a Python 15 | distribution with most of the packages preinstalled. Otherwise, please refer to documentation for each required 16 | package for how to install/build from source. 17 | 18 | Required Packages 19 | ----------------- 20 | In addition to the default packages that come with Anaconda (numpy, scipy, etc.), you'll need the following additional 21 | packages: 22 | 23 | - `subprocess32 `_ (Python 2 only) 24 | 25 | This package is a backport of Python 3.2's subprocess module to Python 2. It is installable from ``pip``. 26 | 27 | - `sqlitedict `_ 28 | 29 | This is a dependency of OpenMDAO. It is installable from ``pip``. 30 | 31 | - `OpenMDAO `_ 32 | 33 | This is a flexible optimization framework in Python developed by NASA. It is installable from ``pip``. 34 | 35 | - `mpich2 `_ (optional) 36 | 37 | This is the Message Passing Interface (MPI) library. OpenMDAO and Pyoptsparse can optionally use this library 38 | for parallel computing. You can install this package with: 39 | 40 | .. code-block:: bash 41 | 42 | > conda install mpich2 43 | 44 | - `mpi4py `_ (optional) 45 | 46 | This is the Python wrapper of ``mpich2``. You can install this package with: 47 | 48 | .. code-block:: bash 49 | 50 | > conda install mpi4py 51 | 52 | - `ipopt `__ (optional) 53 | 54 | `Ipopt `__ is a free software package for large-scale nonlinear optimization. 55 | This can be used to replace the default optimization solver that comes with scipy. You can install this package with: 56 | 57 | .. code-block:: bash 58 | 59 | > conda install --channel pkerichang ipopt 60 | 61 | - `pyoptsparse `_ (optional) 62 | 63 | ``pyoptsparse`` is a python package that contains a collection of optmization solvers, including a Python wrapper 64 | around ``Ipopt``. You can install this package with: 65 | 66 | .. code-block:: bash 67 | 68 | > conda install --channel pkerichang pyoptsparse 69 | -------------------------------------------------------------------------------- /bag/math/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This package defines design template classes. 4 | """ 5 | 6 | from typing import Iterable 7 | 8 | import numpy as np 9 | from . import interpolate 10 | 11 | __all__ = ['lcm', 'gcd', 'interpolate', 'float_to_si_string', 'si_string_to_float'] 12 | 13 | 14 | si_mag = [-18, -15, -12, -9, -6, -3, 0, 3, 6, 9, 12] 15 | si_pre = ['a', 'f', 'p', 'n', 'u', 'm', '', 'k', 'M', 'G', 'T'] 16 | 17 | 18 | def float_to_si_string(num, precision=6): 19 | """Converts the given floating point number to a string using SI prefix. 20 | 21 | Parameters 22 | ---------- 23 | num : float 24 | the number to convert. 25 | precision : int 26 | number of significant digits, defaults to 6. 27 | 28 | Returns 29 | ------- 30 | ans : str 31 | the string representation of the given number using SI suffix. 32 | """ 33 | if abs(num) < 1e-21: 34 | return '0' 35 | exp = np.log10(abs(num)) 36 | 37 | pre_idx = len(si_mag) - 1 38 | for idx in range(len(si_mag)): 39 | if exp < si_mag[idx]: 40 | pre_idx = idx - 1 41 | break 42 | 43 | fmt = '%%.%dg%%s' % precision 44 | res = 10.0 ** (si_mag[pre_idx]) 45 | return fmt % (num / res, si_pre[pre_idx]) 46 | 47 | 48 | def si_string_to_float(si_str): 49 | """Converts the given string with SI prefix to float. 50 | 51 | Parameters 52 | ---------- 53 | si_str : str 54 | the string to convert 55 | 56 | Returns 57 | ------- 58 | ans : float 59 | the floating point value of the given string. 60 | """ 61 | if si_str[-1] in si_pre: 62 | idx = si_pre.index(si_str[-1]) 63 | return float(si_str[:-1]) * 10**si_mag[idx] 64 | else: 65 | return float(si_str) 66 | 67 | 68 | def gcd(a, b): 69 | # type: (int, int) -> int 70 | """Compute greatest common divisor of two positive integers. 71 | 72 | Parameters 73 | ---------- 74 | a : int 75 | the first number. 76 | b : int 77 | the second number. 78 | 79 | Returns 80 | ------- 81 | ans : int 82 | the greatest common divisor of the two given integers. 83 | """ 84 | while b: 85 | a, b = b, a % b 86 | return a 87 | 88 | 89 | def lcm(arr, init=1): 90 | # type: (Iterable[int], int) -> int 91 | """Compute least common multiple of all numbers in the given list. 92 | 93 | Parameters 94 | ---------- 95 | arr : Iterable[int] 96 | a list of integers. 97 | init : int 98 | the initial LCM. Defaults to 1. 99 | 100 | Returns 101 | ------- 102 | ans : int 103 | the least common multiple of all the given numbers. 104 | """ 105 | cur_lcm = init 106 | for val in arr: 107 | cur_lcm = cur_lcm * val // gcd(cur_lcm, val) 108 | return cur_lcm 109 | -------------------------------------------------------------------------------- /bag/data/mos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines classes for computing DC operating point. 4 | """ 5 | 6 | from typing import Dict 7 | 8 | import numpy as np 9 | 10 | 11 | def mos_y_to_ss(sim_data, char_freq, fg, ibias, cfit_method='average'): 12 | # type: (Dict[str, np.ndarray], float, int, np.ndarray, str) -> Dict[str, np.ndarray] 13 | """Convert transistor Y parameters to small-signal parameters. 14 | 15 | This function computes MOSFET small signal parameters from 3-port 16 | Y parameter measurements done on gate, drain and source, with body 17 | bias fixed. This functions fits the Y parameter to a capcitor-only 18 | small signal model using least-mean-square error. 19 | 20 | Parameters 21 | ---------- 22 | sim_data : Dict[str, np.ndarray] 23 | A dictionary of Y parameters values stored as complex numpy arrays. 24 | char_freq : float 25 | the frequency Y parameters are measured at. 26 | fg : int 27 | number of transistor fingers used for the Y parameter measurement. 28 | ibias : np.ndarray 29 | the DC bias current of the transistor. Always positive. 30 | cfit_method : str 31 | method used to extract capacitance from Y parameters. Currently 32 | supports 'average' or 'worst' 33 | 34 | Returns 35 | ------- 36 | ss_dict : Dict[str, np.ndarray] 37 | A dictionary of small signal parameter values stored as numpy 38 | arrays. These values are normalized to 1-finger transistor. 39 | """ 40 | w = 2 * np.pi * char_freq 41 | 42 | gm = (sim_data['y21'].real - sim_data['y31'].real) / 2.0 # type: np.ndarray 43 | gds = (sim_data['y22'].real - sim_data['y32'].real) / 2.0 # type: np.ndarray 44 | gb = (sim_data['y33'].real - sim_data['y23'].real) / 2.0 - gm - gds # type: np.ndarray 45 | 46 | cgd12 = -sim_data['y12'].imag / w 47 | cgd21 = -sim_data['y21'].imag / w 48 | cgs13 = -sim_data['y13'].imag / w 49 | cgs31 = -sim_data['y31'].imag / w 50 | cds23 = -sim_data['y23'].imag / w 51 | cds32 = -sim_data['y32'].imag / w 52 | cgg = sim_data['y11'].imag / w 53 | cdd = sim_data['y22'].imag / w 54 | css = sim_data['y33'].imag / w 55 | 56 | if cfit_method == 'average': 57 | cgd = (cgd12 + cgd21) / 2 # type: np.ndarray 58 | cgs = (cgs13 + cgs31) / 2 # type: np.ndarray 59 | cds = (cds23 + cds32) / 2 # type: np.ndarray 60 | elif cfit_method == 'worst': 61 | cgd = np.maximum(cgd12, cgd21) 62 | cgs = np.maximum(cgs13, cgs31) 63 | cds = np.maximum(cds23, cds32) 64 | else: 65 | raise ValueError('Unknown cfit_method = %s' % cfit_method) 66 | 67 | cgb = cgg - cgd - cgs # type: np.ndarray 68 | cdb = cdd - cds - cgd # type: np.ndarray 69 | csb = css - cgs - cds # type: np.ndarray 70 | 71 | ibias = ibias / fg 72 | gm = gm / fg 73 | gds = gds / fg 74 | gb = gb / fg 75 | cgd = cgd / fg 76 | cgs = cgs / fg 77 | cds = cds / fg 78 | cgb = cgb / fg 79 | cdb = cdb / fg 80 | csb = csb / fg 81 | 82 | return dict( 83 | ibias=ibias, 84 | gm=gm, 85 | gds=gds, 86 | gb=gb, 87 | cgd=cgd, 88 | cgs=cgs, 89 | cds=cds, 90 | cgb=cgb, 91 | cdb=cdb, 92 | csb=csb, 93 | ) 94 | -------------------------------------------------------------------------------- /bag/verification/virtuoso.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module handles exporting schematic/layout from Virtuoso. 4 | """ 5 | 6 | from typing import TYPE_CHECKING, Optional, Dict, Any 7 | 8 | import os 9 | from abc import ABC 10 | 11 | from ..io import write_file, open_temp 12 | from .base import SubProcessChecker 13 | 14 | if TYPE_CHECKING: 15 | from .base import ProcInfo 16 | 17 | 18 | class VirtuosoChecker(SubProcessChecker, ABC): 19 | """the base Checker class for Virtuoso. 20 | 21 | This class implement layout/schematic export procedures. 22 | 23 | Parameters 24 | ---------- 25 | tmp_dir : str 26 | temporary file directory. 27 | max_workers : int 28 | maximum number of parallel processes. 29 | cancel_timeout : float 30 | timeout for cancelling a subprocess. 31 | source_added_file : str 32 | file to include for schematic export. 33 | """ 34 | 35 | def __init__(self, tmp_dir, max_workers, cancel_timeout, source_added_file): 36 | # type: (str, int, float, str) -> None 37 | SubProcessChecker.__init__(self, tmp_dir, max_workers, cancel_timeout) 38 | self._source_added_file = source_added_file 39 | 40 | def setup_export_layout(self, lib_name, cell_name, out_file, view_name='layout', params=None): 41 | # type: (str, str, str, str, Optional[Dict[str, Any]]) -> ProcInfo 42 | out_file = os.path.abspath(out_file) 43 | 44 | run_dir = os.path.dirname(out_file) 45 | out_name = os.path.basename(out_file) 46 | log_file = os.path.join(run_dir, 'layout_export.log') 47 | 48 | os.makedirs(run_dir, exist_ok=True) 49 | 50 | # fill in stream out configuration file. 51 | content = self.render_file_template('layout_export_config.txt', 52 | dict( 53 | lib_name=lib_name, 54 | cell_name=cell_name, 55 | view_name=view_name, 56 | output_name=out_name, 57 | run_dir=run_dir, 58 | )) 59 | 60 | with open_temp(prefix='stream_template', dir=run_dir, delete=False) as config_file: 61 | config_fname = config_file.name 62 | config_file.write(content) 63 | 64 | # run strmOut 65 | cmd = ['strmout', '-templateFile', config_fname] 66 | 67 | return cmd, log_file, None, os.environ['BAG_WORK_DIR'] 68 | 69 | def setup_export_schematic(self, lib_name, cell_name, out_file, view_name='schematic', 70 | params=None): 71 | # type: (str, str, str, str, Optional[Dict[str, Any]]) -> ProcInfo 72 | out_file = os.path.abspath(out_file) 73 | 74 | run_dir = os.path.dirname(out_file) 75 | out_name = os.path.basename(out_file) 76 | log_file = os.path.join(run_dir, 'schematic_export.log') 77 | 78 | # fill in stream out configuration file. 79 | content = self.render_file_template('si_env.txt', 80 | dict( 81 | lib_name=lib_name, 82 | cell_name=cell_name, 83 | view_name=view_name, 84 | output_name=out_name, 85 | source_added_file=self._source_added_file, 86 | run_dir=run_dir, 87 | )) 88 | 89 | # create configuration file. 90 | config_fname = os.path.join(run_dir, 'si.env') 91 | write_file(config_fname, content) 92 | 93 | # run command 94 | cmd = ['si', run_dir, '-batch', '-command', 'netlist'] 95 | 96 | return cmd, log_file, None, os.environ['BAG_WORK_DIR'] 97 | -------------------------------------------------------------------------------- /docs/source/tutorial/collaboration.rst: -------------------------------------------------------------------------------- 1 | Collaboration with BAG 2 | ====================== 3 | 4 | In this tutorial, you will use pre-written schematic, layout, and testbench generators to create a new design and a 5 | testbench, then run post-extraction simulation with the generated designs. After this tutorial, you should have a 6 | good idea of how to use other people's generators. 7 | 8 | #. Check out a BAG workspace directory for your process technology. This tutorial will use TSMC 65 GP as an example. 9 | If you need a BAG workspace for your technology, contact one of the BAG developers. To check out the directory 10 | from git, type the following on the command line: 11 | 12 | .. code-block:: bash 13 | 14 | > git clone git@bwrcrepo.eecs.berkeley.edu:BAG/BAG2_TSMC65_tutorial.git 15 | > mv BAG2-TSMC65GP-WORKSPACE bag_tutorial 16 | > cd bag_tutorial 17 | > source .cshrc 18 | 19 | Note that we renamed the workspace to be :file:`bag_tutorial`. Feel free to use another name. 20 | 21 | #. Next, we need to check out the generators. For good practice, every designer should keep a repository of the 22 | generators they're working on, and other will check out the repository to get the generator code. In :program:`git`, 23 | this is most naturally done with use of submodules, which lets your repository reference other's repositories. 24 | 25 | When you first check out a repository, you need to explicitly tell git to populate the submodules. On the command 26 | line, type: 27 | 28 | .. code-block:: bash 29 | 30 | > git submodule init 31 | > git submodule update 32 | 33 | This will initialize then check out two repositories, :file:`bag_serdes_burst_mode` and :file:`BAG2_TEMPLATES_EC`. 34 | :file:`bag_serdes_burst_mode` contains schematic and testbench generators for burst mode serdes links, and 35 | :file:`BAG2_TEMPLATES_EC` contains layout generators for various analog circuits. 36 | 37 | #. Now that we have the generators, we need to modify several configuration files to let BAG know where to find them. 38 | open the file :file:`cds.lib`, you will see the following two lines: 39 | 40 | .. code-block:: text 41 | 42 | DEFINE serdes_bm_templates $BAG_WORK_DIR/bag_serdes_burst_mode/serdes_bm_templates 43 | DEFINE serdes_bm_testbenches $BAG_WORK_DIR/bag_serdes_burst_mode/serdes_bm_testbenches 44 | 45 | these two lines tells Virtuoso where to find the schematic and testbench generators. 46 | 47 | #. Open the file :file:`bag_libs.def`, you will see the following line: 48 | 49 | .. code-block:: text 50 | 51 | serdes_bm_templates $BAG_WORK_DIR/bag_serdes_burst_mode/BagModules 52 | 53 | this line tells :py:obj:`bag` where it can find the design modules of the schematic generators. 54 | 55 | #. Open the file :file:`start_bag.sh`, you will see the following line: 56 | 57 | .. code-block:: bash 58 | 59 | setenv PYTHONPATH "${BAG_WORK_DIR}/BAG2_TEMPLATES_EC:${BAG_TECH_CONFIG_DIR}" 60 | 61 | this line adds :file:`BAG_2_TEMPLATES_EC` and the technology configuration folder to :envvar:`$PYTHONPATH`, so 62 | :py:obj`bag` can find the layout generators. 63 | 64 | #. Now that the configuration files are set up, we are ready to run the code. Start Virtuoso in the directory, then 65 | in the CIW window (the window that shows log messages), type the following: 66 | 67 | .. code-block:: none 68 | 69 | load("start_bag.il") 70 | 71 | this starts the BAG server. Then, on the command line, type: 72 | 73 | .. code-block:: bash 74 | 75 | > ./sim_server.sh & 76 | 77 | to start the simulation server. Then, o nthe command line, type: 78 | 79 | .. code-block:: bash 80 | 81 | > ./start_bag.sh 82 | 83 | to start the IPython interpreter. Once you're in the interpreter, type: 84 | 85 | .. code-block:: none 86 | 87 | In [1]: run -i demo_scripts/diffamp_tran.py 88 | 89 | this will create a schematic, layout, and testbench in library ``serdes_bm_1``, run LVS and RCX, run 90 | post-extraction transient simulation, then import the data back to Python and plot the output waveform. If you 91 | see a sinusodial waveform plot, the tutorial has finished successfully. 92 | 93 | to see how each of these steps is done, read the script :file:`demo_scripts/diffamp_tran.py`. 94 | -------------------------------------------------------------------------------- /bag/virtuoso.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module provides functions needed to get Virtuoso to work with BAG. 4 | """ 5 | 6 | import os 7 | import sys 8 | import atexit 9 | import signal 10 | import argparse 11 | 12 | import bag.interface 13 | import bag.io 14 | 15 | 16 | def run_skill_server(args): 17 | """Run the BAG/Virtuoso server.""" 18 | error_msg = '' 19 | server = None 20 | port_file = None 21 | port_number = None 22 | 23 | try: 24 | # process command line arguments 25 | min_port = args.min_port 26 | max_port = args.max_port 27 | # remove directory from port file name 28 | port_file = os.path.basename(args.port_file) 29 | log_file = args.log_file 30 | 31 | # create log file directory, and remove old log. 32 | if log_file is not None: 33 | log_file = os.path.abspath(log_file) 34 | log_dir = os.path.dirname(log_file) 35 | if not os.path.exists(log_dir): 36 | os.makedirs(log_dir) 37 | elif os.path.exists(log_file): 38 | os.remove(log_file) 39 | 40 | # determine port file name 41 | if 'BAG_WORK_DIR' not in os.environ: 42 | raise Exception('Environment variable BAG_WORK_DIR not defined') 43 | work_dir = os.environ['BAG_WORK_DIR'] 44 | if not os.path.isdir(work_dir): 45 | raise Exception('$BAG_WORK_DIR = %s is not a directory' % work_dir) 46 | 47 | port_file = os.path.join(work_dir, port_file) 48 | 49 | # determine temp directory 50 | tmp_dir = None 51 | if 'BAG_TEMP_DIR' in os.environ: 52 | tmp_dir = os.environ['BAG_TEMP_DIR'] 53 | if not os.path.isdir(tmp_dir): 54 | if os.path.exists(tmp_dir): 55 | raise Exception('$BAG_TEMP_DIR = %s is not a directory' % tmp_dir) 56 | else: 57 | os.makedirs(tmp_dir) 58 | 59 | # attempt to open port and start server 60 | router = bag.interface.ZMQRouter(min_port=min_port, max_port=max_port, log_file=log_file) 61 | server = bag.interface.SkillServer(router, sys.stdout, sys.stdin, tmpdir=tmp_dir) 62 | port_number = router.get_port() 63 | except Exception as ex: 64 | error_msg = 'bag server process error:\n%s\n' % str(ex) 65 | 66 | if not error_msg: 67 | bag.io.write_file(port_file, '%r\n' % port_number) 68 | 69 | # TODO: somehow this is a bug??!! figure it out. 70 | # make sure port_file is removed at exit 71 | # def exit_handler(): 72 | # if os.path.exists(port_file): 73 | # os.remove(port_file) 74 | 75 | # atexit.register(exit_handler) 76 | # signal.signal(signal.SIGTERM, exit_handler) 77 | 78 | try: 79 | sys.stdout.write('BAG skill server has started. Yay!\n') 80 | sys.stdout.flush() 81 | server.run() 82 | except Exception as ex: 83 | error_msg = 'bag server process error:\n%s\n' % str(ex) 84 | 85 | if error_msg: 86 | sys.stderr.write(error_msg) 87 | sys.stderr.flush() 88 | 89 | 90 | def parse_command_line_arguments(): 91 | """Parse command line arguments, then run the corresponding function.""" 92 | 93 | desc = 'A Python program that performs tasks for virtuoso.' 94 | parser = argparse.ArgumentParser(description=desc) 95 | desc = 'Valid commands. Supply -h/--help flag after the command name to learn more about the command.' 96 | sub_parsers = parser.add_subparsers(title='Commands', description=desc, help='command name.') 97 | 98 | desc = 'Run BAG skill server.' 99 | par2 = sub_parsers.add_parser('run_skill_server', description=desc, help=desc) 100 | 101 | par2.add_argument('min_port', type=int, help='minimum socket port number.') 102 | par2.add_argument('max_port', type=int, help='maximum socket port number.') 103 | par2.add_argument('port_file', type=str, help='file to write the port number to.') 104 | par2.add_argument('log_file', type=str, nargs='?', default=None, 105 | help='log file name.') 106 | par2.set_defaults(func=run_skill_server) 107 | 108 | args = parser.parse_args() 109 | args.func(args) 110 | 111 | 112 | if __name__ == '__main__': 113 | parse_command_line_arguments() 114 | -------------------------------------------------------------------------------- /bag/io/file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module handles file related IO. 4 | """ 5 | 6 | import os 7 | import tempfile 8 | import time 9 | import pkg_resources 10 | import codecs 11 | 12 | import yaml 13 | 14 | from .common import bag_encoding, bag_codec_error 15 | 16 | 17 | def open_file(fname, mode): 18 | """Opens a file with the correct encoding interface. 19 | 20 | Use this method if you need to have a file handle. 21 | 22 | Parameters 23 | ---------- 24 | fname : string 25 | the file name. 26 | mode : string 27 | the mode, either 'r', 'w', or 'a'. 28 | 29 | Returns 30 | ------- 31 | file_obj : file 32 | a file objects that reads/writes string with the BAG system encoding. 33 | """ 34 | if mode != 'r' and mode != 'w' and mode != 'a': 35 | raise ValueError("Only supports 'r', 'w', or 'a' mode.") 36 | return open(fname, mode, encoding=bag_encoding, errors=bag_codec_error) 37 | 38 | 39 | def read_file(fname): 40 | """Read the given file and return content as string. 41 | 42 | Parameters 43 | ---------- 44 | fname : string 45 | the file name. 46 | 47 | Returns 48 | ------- 49 | content : unicode 50 | the content as a unicode string. 51 | """ 52 | with open_file(fname, 'r') as f: 53 | content = f.read() 54 | return content 55 | 56 | 57 | def readlines_iter(fname): 58 | """Iterate over lines in a file. 59 | 60 | Parameters 61 | ---------- 62 | fname : string 63 | the file name. 64 | 65 | Yields 66 | ------ 67 | line : unicode 68 | a line in the file. 69 | """ 70 | with open_file(fname, 'r') as f: 71 | for line in f: 72 | yield line 73 | 74 | 75 | def read_yaml(fname): 76 | """Read the given file using YAML. 77 | 78 | Parameters 79 | ---------- 80 | fname : string 81 | the file name. 82 | 83 | Returns 84 | ------- 85 | content : Any 86 | the object returned by YAML. 87 | """ 88 | with open_file(fname, 'r') as f: 89 | content = yaml.load(f) 90 | 91 | return content 92 | 93 | 94 | def read_resource(package, fname): 95 | """Read the given resource file and return content as string. 96 | 97 | Parameters 98 | ---------- 99 | package : string 100 | the package name. 101 | fname : string 102 | the resource file name. 103 | 104 | Returns 105 | ------- 106 | content : unicode 107 | the content as a unicode string. 108 | """ 109 | raw_content = pkg_resources.resource_string(package, fname) 110 | return raw_content.decode(encoding=bag_encoding, errors=bag_codec_error) 111 | 112 | 113 | def write_file(fname, content, append=False, mkdir=True): 114 | """Writes the given content to file. 115 | 116 | Parameters 117 | ---------- 118 | fname : string 119 | the file name. 120 | content : unicode 121 | the unicode string to write to file. 122 | append : bool 123 | True to append instead of overwrite. 124 | mkdir : bool 125 | If True, will create parent directories if they don't exist. 126 | """ 127 | if mkdir: 128 | fname = os.path.abspath(fname) 129 | dname = os.path.dirname(fname) 130 | os.makedirs(dname, exist_ok=True) 131 | 132 | mode = 'a' if append else 'w' 133 | with open_file(fname, mode) as f: 134 | f.write(content) 135 | 136 | 137 | def make_temp_dir(prefix, parent_dir=None): 138 | """Create a new temporary directory. 139 | 140 | Parameters 141 | ---------- 142 | prefix : string 143 | the directory prefix. 144 | parent_dir : string 145 | the parent directory. 146 | """ 147 | prefix += time.strftime("_%Y%m%d_%H%M%S") 148 | parent_dir = parent_dir or tempfile.gettempdir() 149 | return tempfile.mkdtemp(prefix=prefix, dir=parent_dir) 150 | 151 | 152 | def open_temp(**kwargs): 153 | """Opens a new temporary file for writing with unicode interface. 154 | 155 | Parameters 156 | ---------- 157 | **kwargs 158 | the tempfile keyword arguments. See documentation for 159 | :func:`tempfile.NamedTemporaryFile`. 160 | 161 | Returns 162 | ------- 163 | file : file 164 | the opened file that accepts unicode input. 165 | """ 166 | timestr = time.strftime("_%Y%m%d_%H%M%S") 167 | if 'prefix' in kwargs: 168 | kwargs['prefix'] += timestr 169 | else: 170 | kwargs['prefix'] = timestr 171 | temp = tempfile.NamedTemporaryFile(**kwargs) 172 | return codecs.getwriter(bag_encoding)(temp, errors=bag_codec_error) 173 | -------------------------------------------------------------------------------- /bag/mdao/components.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines various OpenMDAO component classes. 4 | """ 5 | 6 | import numpy as np 7 | import openmdao.api as omdao 8 | 9 | 10 | class VecFunComponent(omdao.Component): 11 | """A component based on a list of functions. 12 | 13 | A component that evaluates multiple functions on the given inputs, then 14 | returns the result as an 1D array. Each of the inputs may be a scalar or 15 | a vector with the same size as the output. If a vector input is given, 16 | each function will use a different element of the vector. 17 | 18 | Parameters 19 | ---------- 20 | output_name : str 21 | output name. 22 | fun_list : list[bag.math.dfun.DiffFunction] 23 | list of interpolator functions, one for each dimension. 24 | params : list[str] 25 | list of parameter names. Parameter names may repeat, in which case the 26 | same parameter will be used for multiple arguments of the function. 27 | vector_params : set[str] 28 | set of parameters that are vector instead of scalar. If a parameter 29 | is a vector, it will be the same size as the output, and each function 30 | only takes in the corresponding element of the parameter. 31 | """ 32 | 33 | def __init__(self, output_name, fun_list, params, 34 | vector_params=None): 35 | omdao.Component.__init__(self) 36 | 37 | vector_params = vector_params or set() 38 | 39 | self._output = output_name 40 | self._out_dim = len(fun_list) 41 | self._in_dim = len(params) 42 | self._params = params 43 | self._unique_params = {} 44 | self._fun_list = fun_list 45 | 46 | for par in params: 47 | adj = par in vector_params 48 | shape = self._out_dim if adj else 1 49 | 50 | if par not in self._unique_params: 51 | # linear check, but small list so should be fine. 52 | self.add_param(par, val=np.zeros(shape)) 53 | self._unique_params[par] = len(self._unique_params), adj 54 | 55 | # construct chain rule jacobian matrix 56 | self._chain_jacobian = np.zeros((self._in_dim, len(self._unique_params))) 57 | for idx, par in enumerate(params): 58 | self._chain_jacobian[idx, self._unique_params[par][0]] = 1 59 | 60 | self.add_output(output_name, val=np.zeros(self._out_dim)) 61 | 62 | def __call__(self, **kwargs): 63 | """Evaluate on the given inputs. 64 | 65 | Parameters 66 | ---------- 67 | kwargs : dict[str, np.array or float] 68 | the inputs as a dictionary. 69 | 70 | Returns 71 | ------- 72 | out : np.array 73 | the output array. 74 | """ 75 | tmp = {} 76 | self.solve_nonlinear(kwargs, tmp) 77 | return tmp[self._output] 78 | 79 | def _get_inputs(self, params): 80 | """Given parameter values, construct inputs for functions. 81 | 82 | Parameters 83 | ---------- 84 | params : VecWrapper, optional 85 | VecWrapper containing parameters. (p) 86 | 87 | Returns 88 | ------- 89 | ans : list[list[float]] 90 | input lists. 91 | """ 92 | ans = np.empty((self._out_dim, self._in_dim)) 93 | for idx, name in enumerate(self._params): 94 | ans[:, idx] = params[name] 95 | return ans 96 | 97 | def solve_nonlinear(self, params, unknowns, resids=None): 98 | """Compute the output parameter. 99 | 100 | Parameters 101 | ---------- 102 | params : VecWrapper, optional 103 | VecWrapper containing parameters. (p) 104 | 105 | unknowns : VecWrapper, optional 106 | VecWrapper containing outputs and states. (u) 107 | 108 | resids : VecWrapper, optional 109 | VecWrapper containing residuals. (r) 110 | """ 111 | xi_mat = self._get_inputs(params) 112 | 113 | tmp = np.empty(self._out_dim) 114 | for idx in range(self._out_dim): 115 | tmp[idx] = self._fun_list[idx](xi_mat[idx, :]) 116 | 117 | unknowns[self._output] = tmp 118 | 119 | def linearize(self, params, unknowns=None, resids=None): 120 | """Compute the Jacobian of the parameter. 121 | 122 | Parameters 123 | ---------- 124 | params : VecWrapper, optional 125 | VecWrapper containing parameters. (p) 126 | 127 | unknowns : VecWrapper, optional 128 | VecWrapper containing outputs and states. (u) 129 | 130 | resids : VecWrapper, optional 131 | VecWrapper containing residuals. (r) 132 | """ 133 | # print('rank {} computing jac for {}'.format(self.comm.rank, self._outputs)) 134 | 135 | xi_mat = self._get_inputs(params) 136 | 137 | jf = np.empty((self._out_dim, self._in_dim)) 138 | for k, fun in enumerate(self._fun_list): 139 | jf[k, :] = fun.jacobian(xi_mat[k, :]) 140 | 141 | jmat = np.dot(jf, self._chain_jacobian) 142 | jdict = {} 143 | for par, (pidx, adj) in self._unique_params.items(): 144 | tmp = jmat[:, pidx] 145 | if adj: 146 | tmp = np.diag(tmp) 147 | jdict[self._output, par] = tmp 148 | 149 | return jdict 150 | -------------------------------------------------------------------------------- /docs/source/overview/design.rst: -------------------------------------------------------------------------------- 1 | Design Module 2 | ============= 3 | 4 | A design module is a Python class that generates new schematics. It computes all parameters needed to generate a 5 | schematic from user defined specifications. For example, a design module for an inverter needs to compute the width, 6 | length, and threshold flavor of the NMOS and PMOS to generate a new inverter schematic. The designer of this module can 7 | let the user specify these parameters directly, or alternatively compute them from higher level specifications, such as 8 | fanout, input capacitance, and leakage specs. 9 | 10 | To create a default design module for a schematic generator, create a :class:`~bag.BagProject` instance and call 11 | :meth:`~bag.BagProject.import_design_library` to import all schematic generators in a library from your CAD 12 | program into Python. The designer should then implement the three methods, :meth:`~bag.design.Module.design`, 13 | :meth:`~bag.design.Module.get_layout_params`, and :meth:`~bag.design.Module.get_layout_pin_mapping` (The latter two are 14 | optional if you do not use BAG to generate layout). Once you finish the design module definition, you can create new 15 | design module instances by calling :meth:`~bag.BagProject.create_design_module`. 16 | 17 | 18 | The following sections describe how each of these methods should be implemented. 19 | 20 | design() 21 | -------- 22 | 23 | This method computes all parameters needed to generate a schematic from user defined specifications. The input 24 | arguments should also be specified in this method. 25 | 26 | A design module can have multiple design methods, as long as they have difference names. For example, You can implement 27 | the ``design()`` method to compute parameters from high level specifications, and define a new method named 28 | ``design_override()`` that allows the user to assign parameter values directly for debugging purposes. 29 | 30 | To enable hierarchical design, design module has a dictionary, :attr:`~bag.design.Module.instances`, that 31 | maps children instance names to corresponding design modules, so you can simply call their 32 | :meth:`~bag.design.Module.design` methods to set their parameters. See :doc:`/tutorial/tutorial` for an simple example. 33 | 34 | If you need to modify the schematic structure (such as adding more inverter buffers), you should call the corresponding 35 | methods before calling :meth:`~bag.design.Module.design` methods of child instances, as those design module could be 36 | changed. The rest of this section explains how you modify the schematic. 37 | 38 | Pin Renaming 39 | ^^^^^^^^^^^^ 40 | 41 | Most of the time, you should not rename the pin of schematic. The only time you should rename the pin is when you have 42 | a variable bus pin where the number of bits in the bus can change with the design. In this case, call 43 | :meth:`~bag.design.Module.rename_pin` to change the number of bits in the bus. To connect/remove instances from 44 | the added/deleted bus pins, see :ref:`instance_connection_modification` 45 | 46 | Delete Instances 47 | ^^^^^^^^^^^^^^^^ 48 | 49 | Delete a child instance by calling :meth:`~bag.design.Module.delete_instance`. After 50 | this call, the corresponding value in :attr:`~bag.design.Module.instances` dictionary will become ``None``. 51 | 52 | .. note:: 53 | You don't have to delete 0-width or 0-finger transistors; BAG already handles that for you. 54 | 55 | Replace Instance Master 56 | ^^^^^^^^^^^^^^^^^^^^^^^ 57 | 58 | If you have two different designs of a child instance, and you want to swap between the two designs, you can call 59 | :meth:`~bag.design.Module.replace_instance_master` to change the instance master of a child. 60 | 61 | .. note:: 62 | You can replace instance masters only if the two instance masters have exactly the symbol, including pin names. 63 | 64 | .. _instance_connection_modification: 65 | 66 | Instance Connection Modification 67 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 68 | 69 | Call :meth:`~bag.design.Module.reconnect_instance_terminal` to change a child instance's connection. 70 | 71 | Arraying Child Instances 72 | ^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | Call :meth:`~bag.design.Module.array_instance` to array a child instance. After this call, 75 | :attr:`~bag.design.Module.instances` will map the child instance name to a list of design modules, one for each instance 76 | in the array. You can then iterate through this list and design each of the instances. They do not need to have the 77 | same parameter values. 78 | 79 | Restoring to Default 80 | ^^^^^^^^^^^^^^^^^^^^ 81 | 82 | If you are using the design module in a design iteration loop, or you're using BAG interactively through the Python 83 | console, and you want to restore a deleted/replaced/arrayed child instance to the default state, you can call 84 | :meth:`~bag.design.Module.restore_instance`. 85 | 86 | 87 | get_layout_params() 88 | ------------------- 89 | 90 | This method should return a dictionary from layout parameter names to their values. This dictionary is used to create 91 | a layout cell that will pass LVS against the generated schematic. 92 | 93 | get_layout_pin_mapping() 94 | ------------------------ 95 | 96 | This method should return a dictionary from layout pin names to schematic pin names. This method exists because a 97 | layout cell may not have the same pin names as the schematic. If a layout pin should be left un-exported, its 98 | corresponding value in the dictionary must be ``None``. 99 | 100 | This dictionary only need to list the layout pins that needs to be renamed. If no renaming is necessary, an empty 101 | dictionary can be returned. 102 | -------------------------------------------------------------------------------- /run_scripts/setup_submodules.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # crazy black magic from: 4 | # https://unix.stackexchange.com/questions/20880/how-can-i-use-environment-variables-in-my-shebang 5 | # this block of code is valid in both bash and python. 6 | # this means if this script is run under bash, it'll 7 | # call this script again using BAG_PYTHON. If 8 | # this script is run under Python, this block of code 9 | # effectively does nothing. 10 | if "true" : '''\' 11 | then 12 | if [[ $BAG_PYTHON ]]; then 13 | exec ${BAG_PYTHON} "$0" "$@" 14 | else 15 | echo "BAG_PYTHON environment variable is not set" 16 | fi 17 | exit 127 18 | fi 19 | ''' 20 | import os 21 | import subprocess 22 | 23 | import yaml 24 | 25 | 26 | def write_to_file(fname, lines): 27 | with open(fname, 'w') as f: 28 | f.writelines((l + '\n' for l in lines)) 29 | add_git_file(fname) 30 | 31 | 32 | def setup_python_path(module_list): 33 | lines = ['# -*- coding: utf-8 -*-', 34 | 'import os', 35 | 'import sys', 36 | '', 37 | "sys.path.append(os.environ['BAG_FRAMEWORK'])", 38 | "sys.path.append(os.environ['BAG_TECH_CONFIG_DIR'])", 39 | ] 40 | template = "sys.path.append(os.path.join(os.environ['BAG_WORK_DIR'], '%s'))" 41 | lines.append(template % 'BAG2_TEMPLATES_EC') 42 | for mod_name, _ in module_list: 43 | lines.append(template % mod_name) 44 | 45 | write_to_file('bag_startup.py', lines) 46 | 47 | 48 | def get_sch_libraries(mod_name, mod_info): 49 | bag_modules = mod_info.get('lib_path', 'BagModules') 50 | root_dir = os.path.realpath(os.path.join(mod_name, bag_modules)) 51 | if not os.path.isdir(root_dir): 52 | return [] 53 | return [name for name in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, name))] 54 | 55 | 56 | def setup_libs_def(module_list): 57 | lines = ['BAG_prim $BAG_TECH_CONFIG_DIR/DesignModules'] 58 | template = '%s $BAG_WORK_DIR/%s/%s' 59 | for mod_name, mod_info in module_list: 60 | bag_modules = mod_info.get('lib_path', 'BagModules') 61 | for lib_name in get_sch_libraries(mod_name, mod_info): 62 | lines.append(template % (lib_name, mod_name, bag_modules)) 63 | 64 | write_to_file('bag_libs.def', lines) 65 | 66 | 67 | def setup_cds_lib(module_list): 68 | lines = ['DEFINE BAG_prim $BAG_TECH_CONFIG_DIR/BAG_prim'] 69 | template = 'DEFINE %s $BAG_WORK_DIR/%s/%s' 70 | for mod_name, mod_info in module_list: 71 | for lib_name in get_sch_libraries(mod_name, mod_info): 72 | lines.append(template % (lib_name, mod_name, lib_name)) 73 | 74 | write_to_file('cds.lib.bag', lines) 75 | 76 | 77 | def run_command(cmd): 78 | timeout = 5 79 | proc = subprocess.Popen(cmd) 80 | try: 81 | proc.communicate() 82 | except KeyboardInterrupt: 83 | print('Ctrl-C detected, terminating') 84 | if proc.returncode is None: 85 | proc.terminate() 86 | print('terminating process...') 87 | try: 88 | proc.wait(timeout=timeout) 89 | print('process terminated') 90 | except subprocess.TimeoutExpired: 91 | proc.kill() 92 | print('process did not terminate, try killing...') 93 | try: 94 | proc.wait(timeout=timeout) 95 | print('process killed') 96 | except subprocess.TimeoutExpired: 97 | print('cannot kill process...') 98 | 99 | if proc.returncode is None: 100 | raise ValueError('Ctrl-C detected, but cannot kill process') 101 | elif proc.returncode < 0: 102 | raise ValueError('process terminated with return code = %d' % proc.returncode) 103 | elif proc.returncode > 0: 104 | raise ValueError('command %s failed' % ' '.join(cmd)) 105 | 106 | 107 | def add_git_submodule(module_name, url): 108 | if os.path.exists(module_name): 109 | # skip if already exists 110 | return 111 | 112 | run_command(['git', 'submodule', 'add', url]) 113 | 114 | 115 | def add_git_file(fname): 116 | run_command(['git', 'add', '-f', fname]) 117 | 118 | 119 | def link_submodule(repo_path, module_name): 120 | if os.path.exists(module_name): 121 | # skip if already exists 122 | return 123 | 124 | src = os.path.join(repo_path, module_name) 125 | if not os.path.isdir(src): 126 | raise ValueError('Cannot find submodule %s in %s' % (module_name, repo_path)) 127 | os.symlink(src, module_name) 128 | add_git_file(module_name) 129 | 130 | 131 | def setup_git_submodules(module_list): 132 | add_git_submodule('BAG2_TEMPLATES_EC', 'git@github.com:ucb-art/BAG2_TEMPLATES_EC') 133 | 134 | for module_name, module_info in module_list: 135 | add_git_submodule(module_name, module_info['url']) 136 | 137 | 138 | def setup_submodule_links(module_list, repo_path): 139 | link_submodule(repo_path, 'BAG2_TEMPLATES_EC') 140 | for module_name, _ in module_list: 141 | link_submodule(repo_path, module_name) 142 | 143 | 144 | def run_main(): 145 | with open('bag_submodules.yaml', 'r') as f: 146 | modules_info = yaml.load(f) 147 | 148 | module_list = [(key, modules_info[key]) for key in sorted(modules_info.keys())] 149 | 150 | # error checking 151 | bag_dir = 'BAG_framework' 152 | if not os.path.isdir(bag_dir): 153 | raise ValueError('Cannot find directory %s' % bag_dir) 154 | 155 | # get real absolute path of parent directory of BAG_framework 156 | repo_path = os.path.dirname(os.path.realpath(bag_dir)) 157 | cur_path = os.path.realpath('.') 158 | if cur_path == repo_path: 159 | # BAG_framework is an actual directory in this repo; add dependencies as git submodules 160 | setup_git_submodules(module_list) 161 | else: 162 | setup_submodule_links(module_list, repo_path) 163 | 164 | setup_python_path(module_list) 165 | setup_libs_def(module_list) 166 | setup_cds_lib(module_list) 167 | 168 | 169 | if __name__ == '__main__': 170 | run_main() 171 | -------------------------------------------------------------------------------- /docs/source/setup/bag_config/database/database.rst: -------------------------------------------------------------------------------- 1 | database 2 | ======== 3 | 4 | This entry defines all settings related to Virtuoso. 5 | 6 | 7 | data.class 8 | ---------- 9 | 10 | The Python class that handles database interaction. This entry is mainly to support non-Virtuoso CAD programs. If you 11 | use Virtuoso, the value must be ``bag.interface.skill.SkillInterface``. 12 | 13 | database.schematic 14 | ------------------ 15 | 16 | This entry contains all settings needed to read/generate schematics. 17 | 18 | .. _sch_tech_lib: 19 | 20 | database.schematic.tech_lib 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | 23 | Technology library. When BAG create new libraries, they will be attached to this technology library. Usually this is 24 | the PDK library provided by the foundry. 25 | 26 | .. _sch_sympin: 27 | 28 | database.schematic.sympin 29 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | 31 | Instance master of symbol pins. This is a list of library/cell/view names. Most of the time this should be 32 | ``["basic", "sympin", "symbolNN"]``. 33 | 34 | .. _sch_ipin: 35 | 36 | database.schematic.ipin 37 | ^^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | Instance master of input pins in schematic. This is a list of library/cell/view names. Most of the time this should be 40 | ``["basic", "ipin", "symbol"]``. 41 | 42 | .. _sch_opin: 43 | 44 | database.schematic.opin 45 | ^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | Instance master of output pins in schematic. This is a list of library/cell/view names. Most of the time this should be 48 | ``["basic", "opin", "symbol"]``. 49 | 50 | .. _sch_iopin: 51 | 52 | database.schematic.iopin 53 | ^^^^^^^^^^^^^^^^^^^^^^^^ 54 | 55 | Instance master of inout pins in schematic. This is a list of library/cell/view names. Most of the time this should be 56 | ``["basic", "iopin", "symbolr"]``. 57 | 58 | .. _sch_simulators: 59 | 60 | database.schematic.simulators 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | A list of simulators where the ``termOrder`` CDF field should be defined. 64 | 65 | When Virtuoso convert schematics to netlists, it uses the ``termOrder`` CDF field to decide how to order the pin names 66 | in the netlist. This entry makes BAG update the ``termOrder`` field correctly whenever pins are changed. 67 | 68 | Most of the time, this should be ``["auLvs", "auCdl", "spectre", "hspiceD"]``. 69 | 70 | .. _sch_exclude: 71 | 72 | database.schematic.exclude_libraries 73 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 74 | 75 | A list of libraries to exclude when importing schematic generators to BAG. Most of the time, this should be 76 | ``["analogLib", "basic", {PDK}]``, where ``{PDK}`` is the PDK library. 77 | 78 | database.testbench 79 | ------------------ 80 | 81 | This entry contains all settings needed to create new testbenches. 82 | 83 | .. _tb_config_libs: 84 | 85 | database.testbench.config_libs 86 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 87 | 88 | A string of config view global libries, separated by spaces. Used to generate config view. 89 | 90 | database.testbench.config_views 91 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 92 | 93 | A string of config view global cellviews, separated by spaces. Used to generate config view. Most of the time this 94 | should be ``"spectre calibre schematic veriloga"``. 95 | 96 | database.testbench.config_stops 97 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 98 | 99 | A string of config view global stop cellviews, separated by spaces. Used to generate config view. Most of the time this 100 | should be ``"spectre veriloga"``. 101 | 102 | .. _sim_env_file: 103 | 104 | database.testbench.env_file 105 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 106 | 107 | The simulation environment file name. A simulation environment is a combination of process corner and temperature. 108 | For example, if you simulate your circuit at TT corner with a temperature of 50 degrees Celsius, you may say the 109 | simulation environment is TT_50. A simulation environment file contains all simulation environments you want to define 110 | when BAG creates a new testbench. This file can be generated by exporting corner setup from an ADE-XL view. 111 | 112 | database.testbench.def_files 113 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 114 | 115 | A list of ADE/spectre definition files to include. Sometimes, a process technology uses definition files 116 | in addition to model files. If so, you can specify definition files to include here as a list of strings. 117 | Use an empty list (``[]``) if no definition file is needed. 118 | 119 | database.testbench.default_env 120 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 121 | 122 | The default simulation environment name. See :ref:`sim_env_file`. 123 | 124 | database.checker 125 | ---------------- 126 | 127 | This entry contains all settings needed to run LVS/RCX from BAG. 128 | 129 | database.checker.checker_cls 130 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 131 | 132 | The Python class that handles LVS/RCX. If you use Calibre with Virtuoso for LVS/RCX, the value must be 133 | ``bag.verification.calibre.Calibre``. 134 | 135 | .. _lvs_rundir: 136 | 137 | database.checker.lvs_run_dir 138 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 139 | 140 | LVS run directory. 141 | 142 | .. _rcx_rundir: 143 | 144 | database.checker.rcx_run_dir 145 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 146 | 147 | RCX run directory 148 | 149 | .. _lvs_runset: 150 | 151 | database.checker.lvs_runset 152 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 153 | 154 | LVS runset. 155 | 156 | .. _rcx_runset: 157 | 158 | database.checker.rcx_runset 159 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 160 | 161 | RCX runset. 162 | 163 | database.checker.source_added_file 164 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 165 | 166 | Location of the source.added file for Calibre LVS. If this entry is not defined, BAG 167 | defaults to ``$DK/Calibre/lvs/source.added``. 168 | 169 | database.checker.rcx_mode 170 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 171 | 172 | Whether to use Calibre PEX or Calibre XACT3D flow to perform parasitic extraction. The 173 | value should be either ``pex`` or ``xact``. If this entry is not defined, BAG defaults to 174 | ``pex``. 175 | 176 | database.checker.xact_rules 177 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 178 | 179 | Location of the Calibre XACT3D rules file. This entry must be defined if using Calibre XACT3D flow. 180 | 181 | 182 | database.calibreview 183 | -------------------- 184 | 185 | This entry contains all settings needed to generate calibre view after RCX. 186 | 187 | .. _calibre_cellmap: 188 | 189 | database.calibreview.cell_map 190 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 191 | 192 | The calibre view cellmap file. 193 | 194 | database.calibreview.view_name 195 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 196 | 197 | view name for calibre view. Usually ``calibre``. 198 | -------------------------------------------------------------------------------- /bag/interface/ocean.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module implements bag's interaction with an ocean simulator. 4 | """ 5 | 6 | from typing import TYPE_CHECKING, Dict, Any, Optional 7 | 8 | import os 9 | 10 | import bag.io 11 | from .simulator import SimProcessManager 12 | 13 | if TYPE_CHECKING: 14 | from .simulator import ProcInfo 15 | 16 | 17 | class OceanInterface(SimProcessManager): 18 | """This class handles interaction with Ocean simulators. 19 | 20 | Parameters 21 | ---------- 22 | tmp_dir : str 23 | temporary file directory for SimAccess. 24 | sim_config : Dict[str, Any] 25 | the simulation configuration dictionary. 26 | """ 27 | 28 | def __init__(self, tmp_dir, sim_config): 29 | # type: (str, Dict[str, Any]) -> None 30 | """Initialize a new SkillInterface object. 31 | """ 32 | SimProcessManager.__init__(self, tmp_dir, sim_config) 33 | 34 | def format_parameter_value(self, param_config, precision): 35 | # type: (Dict[str, Any], int) -> str 36 | """Format the given parameter value as a string. 37 | 38 | To support both single value parameter and parameter sweeps, each parameter value is 39 | represented as a string instead of simple floats. This method will cast a parameter 40 | configuration (which can either be a single value or a sweep) to a 41 | simulator-specific string. 42 | 43 | Parameters 44 | ---------- 45 | param_config: Dict[str, Any] 46 | a dictionary that describes this parameter value. 47 | 48 | 4 formats are supported. This is best explained by example. 49 | 50 | single value: 51 | dict(type='single', value=1.0) 52 | 53 | sweep a given list of values: 54 | dict(type='list', values=[1.0, 2.0, 3.0]) 55 | 56 | linear sweep with inclusive start, inclusive stop, and step size: 57 | dict(type='linstep', start=1.0, stop=3.0, step=1.0) 58 | 59 | logarithmic sweep with given number of points per decade: 60 | dict(type='decade', start=1.0, stop=10.0, num=10) 61 | 62 | precision : int 63 | the parameter value precision. 64 | 65 | Returns 66 | ------- 67 | param_str : str 68 | a string representation of param_config 69 | """ 70 | 71 | fmt = '%.{}e'.format(precision) 72 | swp_type = param_config['type'] 73 | if swp_type == 'single': 74 | return fmt % param_config['value'] 75 | elif swp_type == 'list': 76 | return ' '.join((fmt % val for val in param_config['values'])) 77 | elif swp_type == 'linstep': 78 | syntax = '{From/To}Linear:%s:%s:%s{From/To}' % (fmt, fmt, fmt) 79 | return syntax % (param_config['start'], param_config['step'], param_config['stop']) 80 | elif swp_type == 'decade': 81 | syntax = '{From/To}Decade:%s:%s:%s{From/To}' % (fmt, '%d', fmt) 82 | return syntax % (param_config['start'], param_config['num'], param_config['stop']) 83 | else: 84 | raise Exception('Unsupported param_config: %s' % param_config) 85 | 86 | def _get_ocean_info(self, save_dir, script_fname, log_fname): 87 | """Private helper function that launches ocean process.""" 88 | # get the simulation command. 89 | sim_kwargs = self.sim_config['kwargs'] 90 | ocn_cmd = sim_kwargs['command'] 91 | env = sim_kwargs.get('env', None) 92 | cwd = sim_kwargs.get('cwd', None) 93 | sim_cmd = [ocn_cmd, '-nograph', '-replay', script_fname, '-log', log_fname] 94 | 95 | if cwd is None: 96 | # set working directory to BAG_WORK_DIR if None 97 | cwd = os.environ['BAG_WORK_DIR'] 98 | 99 | # create empty log file to make sure it exists. 100 | return sim_cmd, log_fname, env, cwd, save_dir 101 | 102 | def setup_sim_process(self, lib, cell, outputs, precision, sim_tag): 103 | # type: (str, str, Dict[str, str], int, Optional[str]) -> ProcInfo 104 | 105 | sim_tag = sim_tag or 'BagSim' 106 | job_options = self.sim_config['job_options'] 107 | init_file = self.sim_config['init_file'] 108 | view = self.sim_config['view'] 109 | state = self.sim_config['state'] 110 | 111 | # format job options as skill list of string 112 | job_opt_str = "'( " 113 | for key, val in job_options.items(): 114 | job_opt_str += '"%s" "%s" ' % (key, val) 115 | job_opt_str += " )" 116 | 117 | # create temporary save directory and log/script names 118 | save_dir = bag.io.make_temp_dir(prefix='%s_data' % sim_tag, parent_dir=self.tmp_dir) 119 | log_fname = os.path.join(save_dir, 'ocn_output.log') 120 | script_fname = os.path.join(save_dir, 'run.ocn') 121 | 122 | # setup ocean simulation script 123 | script = self.render_file_template('run_simulation.ocn', 124 | dict( 125 | lib=lib, 126 | cell=cell, 127 | view=view, 128 | state=state, 129 | init_file=init_file, 130 | save_dir=save_dir, 131 | precision=precision, 132 | sim_tag=sim_tag, 133 | outputs=outputs, 134 | job_opt_str=job_opt_str, 135 | )) 136 | bag.io.write_file(script_fname, script) 137 | 138 | return self._get_ocean_info(save_dir, script_fname, log_fname) 139 | 140 | def setup_load_process(self, lib, cell, hist_name, outputs, precision): 141 | # type: (str, str, str, Dict[str, str], int) -> ProcInfo 142 | 143 | init_file = self.sim_config['init_file'] 144 | view = self.sim_config['view'] 145 | 146 | # create temporary save directory and log/script names 147 | save_dir = bag.io.make_temp_dir(prefix='%s_data' % hist_name, parent_dir=self.tmp_dir) 148 | log_fname = os.path.join(save_dir, 'ocn_output.log') 149 | script_fname = os.path.join(save_dir, 'run.ocn') 150 | 151 | # setup ocean load script 152 | script = self.render_file_template('load_results.ocn', 153 | dict( 154 | lib=lib, 155 | cell=cell, 156 | view=view, 157 | init_file=init_file, 158 | save_dir=save_dir, 159 | precision=precision, 160 | hist_name=hist_name, 161 | outputs=outputs, 162 | )) 163 | bag.io.write_file(script_fname, script) 164 | 165 | # launch ocean 166 | return self._get_ocean_info(save_dir, script_fname, log_fname) 167 | -------------------------------------------------------------------------------- /bag/interface/templates/load_results.ocn: -------------------------------------------------------------------------------- 1 | lib = "{{ lib }}" 2 | cell = "{{ cell }}" 3 | view = "{{ view }}" 4 | init_file = "{{ init_file }}" 5 | save_dir = "{{ save_dir }}" 6 | precision = {{ precision }} 7 | hist_name = "{{ hist_name }}" 8 | 9 | ; initialize environment variables 10 | when( strlen(init_file) > 0 11 | load(init_file) 12 | ) 13 | 14 | ; save parametric waveform values as a flattened list. 15 | procedure( save_param_wave_values(wave fmt line_fmt fhandle) 16 | let( (vec wave_cls tmp_val) 17 | if( drIsWaveform(wave) then 18 | ; 1D waveform, simply print all values 19 | vec = drGetWaveformYVec(wave) 20 | wave_cls = className(classOf(drGetElem(vec 0))) 21 | if( wave_cls == 'adtComplex then 22 | ; print complex 23 | for( i 0 drVectorLength(vec) - 1 24 | tmp_val = drGetElem(vec i) 25 | if( imag(tmp_val) < 0 then 26 | ; fix for negative imaginary part. 27 | sprintf(line_fmt "%s%sj\n" fmt fmt) 28 | else 29 | sprintf(line_fmt "%s+%sj\n" fmt fmt) 30 | ) 31 | fprintf(fhandle line_fmt real(tmp_val) imag(tmp_val)) 32 | ) 33 | else 34 | ; print real value 35 | for( i 0 drVectorLength(vec) - 1 36 | fprintf(fhandle line_fmt drGetElem(vec i)) 37 | ) 38 | ) 39 | else 40 | ; parametric waveform, recurse 41 | foreach(val sweepValues(wave) 42 | save_param_wave_values(famValue(wave val) fmt line_fmt fhandle) 43 | ) 44 | ) 45 | ) 46 | ) 47 | 48 | 49 | ; define save functions 50 | ; save a waveform to file. 51 | ; the given waveform will be saved to the file "/.data" as a flattened 1D array. 52 | ; the sweep parameter names of this waveform will be saved to the file "/.sweep", 53 | ; and the values of each parameter will be saved to the file "/.info". 54 | ; data_list_struct is a tconc struct of (waveform_name, waveform_data_file_handle) pairs. 55 | procedure( save_waveform(directory var_name wave precision data_list_struct) 56 | let( (fmt line_fmt wave_cls entry data_file sweep_file fhandle 57 | name_list val_list sweep_df iter_wave) 58 | sprintf(fmt "%%.%de" precision) 59 | sprintf(line_fmt "%s\n" fmt) 60 | wave_cls = className(classOf(wave)) 61 | 62 | if( not( entry = assoc( var_name cdar(data_list_struct) ) ) then 63 | ; first time saving this variable 64 | sprintf(data_file "%s/%s.data" directory var_name) 65 | sprintf(sweep_file "%s/%s.sweep" directory var_name) 66 | cond( 67 | ( or( drIsWaveform(wave) drIsParamWave(wave) ) 68 | ; save sweep names 69 | fhandle = outfile( sweep_file "w" ) 70 | name_list = sweepNames(wave) 71 | foreach(swp_name name_list 72 | fprintf(fhandle "%s\n" swp_name) 73 | ) 74 | close(fhandle) 75 | 76 | ; save sweep values 77 | iter_wave = wave 78 | foreach(swp_name name_list 79 | ; save output most sweep values 80 | val_list = sweepValues(iter_wave) 81 | sprintf(sweep_df "%s/%s.info" directory swp_name) 82 | unless( isFile(sweep_df) 83 | fhandle = outfile( sweep_df "w" ) 84 | foreach(val val_list 85 | fprintf(fhandle line_fmt val) 86 | ) 87 | close(fhandle) 88 | ) 89 | ; remove outer sweep 90 | when( drIsParamWave(iter_wave) 91 | iter_wave = famValue(iter_wave car(val_list)) 92 | ) 93 | ) 94 | 95 | fhandle = outfile( data_file "w" ) 96 | ) 97 | ( or( wave_cls == 'flonum wave_cls == 'fixnum wave_cls == 'adtComplex ) 98 | ; scalar data, make empty sweep file 99 | fhandle = outfile( sweep_file "w") 100 | close(fhandle) 101 | fhandle = outfile( data_file "w" ) 102 | ) 103 | ( t 104 | ; unsupported type 105 | error("Unsupported data for output %s: %A\n" var_name wave) 106 | ) 107 | ) 108 | tconc( data_list_struct list(var_name fhandle) ) 109 | else 110 | fhandle = cadr(entry) 111 | ) 112 | 113 | ; append data to file 114 | if( or( drIsWaveform(wave) drIsParamWave(wave) ) then 115 | save_param_wave_values(wave fmt line_fmt fhandle) 116 | else 117 | ; print single point value 118 | if( wave_cls == 'adtComplex then 119 | ; print complex 120 | if( imag(wave) < 0 then 121 | ; fix for negative imaginary part. 122 | sprintf(line_fmt "%s%sj\n" fmt fmt) 123 | else 124 | sprintf(line_fmt "%s+%sj\n" fmt fmt) 125 | ) 126 | fprintf(fhandle line_fmt real(wave) imag(wave)) 127 | else 128 | fprintf(fhandle line_fmt wave) 129 | ) 130 | ) 131 | 't 132 | ) 133 | ) 134 | 135 | ocnSetXLMode() 136 | ocnxlTargetCellView(lib cell view) 137 | 138 | ; load result database 139 | rdb = axlReadHistoryResDB(hist_name) 140 | unless( rdb 141 | error("Cannot find database associated with name %s" hist_name) 142 | ) 143 | point_list = rdb->points() 144 | 145 | sprintf(sweep_fname "%s/sweep.info" save_dir) 146 | sweep_f = outfile( sweep_fname "w" ) 147 | 148 | ; write sweep parameters title 149 | when( point_list 150 | point = car(point_list) 151 | test_list = point->tests() 152 | when( test_list 153 | corner = car(test_list)->cornerName 154 | par_names = setof( name point->params(?corner corner ?sortBy 'name)~>name 155 | and( (name != "corModelSpec") (name != "temperature") ) ) 156 | 157 | fprintf(sweep_f "corner ") 158 | fprintf(sweep_f "%s\n" buildString( par_names " " )) 159 | ) 160 | ) 161 | 162 | ; iterate through each design point and save data. 163 | data_list_struct = tconc(nil 0) 164 | total_points = length(point_list) 165 | cur_idx = 1 166 | foreach(point point_list 167 | printf("*Info* saving process: %d/%d\n" cur_idx total_points) 168 | cur_idx = cur_idx + 1 169 | foreach(test point->tests() 170 | ; write param values to file. 171 | corner = test->cornerName 172 | params = setof(par point->params(?corner corner ?sortBy 'name) 173 | and( (par->name != "corModelSpec") (par->name != "temperature") ) ) 174 | param_vals = mapcar( lambda( (par) par->valueAsString(?digits precision ?notation 'eng) ) params ) 175 | fprintf(sweep_f "%s " corner) 176 | fprintf(sweep_f "%s\n" buildString( param_vals " " )) 177 | 178 | ; open results 179 | openResults(test->resultsDir) 180 | 181 | {% for var, expr in outputs.items() %} 182 | tmp = {{ expr }} 183 | save_waveform( save_dir "{{ var }}" tmp precision data_list_struct ) 184 | {% endfor %} 185 | 186 | ) 187 | ) 188 | 189 | ; close opened files 190 | close(sweep_f) 191 | foreach( entry cdar(data_list_struct) 192 | close(cadr(entry)) 193 | ) 194 | 195 | ocnxlEndXLMode() 196 | 197 | exit() 198 | -------------------------------------------------------------------------------- /bag/interface/templates/run_simulation.ocn: -------------------------------------------------------------------------------- 1 | lib = "{{ lib }}" 2 | cell = "{{ cell }}" 3 | view = "{{ view }}" 4 | state = "{{ state }}" 5 | init_file = "{{ init_file }}" 6 | save_dir = "{{ save_dir }}" 7 | precision = {{ precision }} 8 | sim_tag = "{{ sim_tag }}" 9 | job_opt_list = {{ job_opt_str }} 10 | 11 | ; initialize environment variables 12 | when( strlen(init_file) > 0 13 | load(init_file) 14 | ) 15 | 16 | ; save parametric waveform values as a flattened list. 17 | procedure( save_param_wave_values(wave fmt line_fmt fhandle) 18 | let( (vec wave_cls tmp_val) 19 | if( drIsWaveform(wave) then 20 | ; 1D waveform, simply print all values 21 | vec = drGetWaveformYVec(wave) 22 | wave_cls = className(classOf(drGetElem(vec 0))) 23 | if( wave_cls == 'adtComplex then 24 | ; print complex 25 | for( i 0 drVectorLength(vec) - 1 26 | tmp_val = drGetElem(vec i) 27 | if( imag(tmp_val) < 0 then 28 | ; fix for negative imaginary part. 29 | sprintf(line_fmt "%s%sj\n" fmt fmt) 30 | else 31 | sprintf(line_fmt "%s+%sj\n" fmt fmt) 32 | ) 33 | fprintf(fhandle line_fmt real(tmp_val) imag(tmp_val)) 34 | ) 35 | else 36 | ; print real value 37 | for( i 0 drVectorLength(vec) - 1 38 | fprintf(fhandle line_fmt drGetElem(vec i)) 39 | ) 40 | ) 41 | else 42 | ; parametric waveform, recurse 43 | foreach(val sweepValues(wave) 44 | save_param_wave_values(famValue(wave val) fmt line_fmt fhandle) 45 | ) 46 | ) 47 | ) 48 | ) 49 | 50 | 51 | ; define save functions 52 | ; save a waveform to file. 53 | ; the given waveform will be saved to the file "/.data" as a flattened 1D array. 54 | ; the sweep parameter names of this waveform will be saved to the file "/.sweep", 55 | ; and the values of each parameter will be saved to the file "/.info". 56 | ; data_list_struct is a tconc struct of (waveform_name, waveform_data_file_handle) pairs. 57 | procedure( save_waveform(directory var_name wave precision data_list_struct) 58 | let( (fmt line_fmt wave_cls entry data_file sweep_file fhandle 59 | name_list val_list sweep_df iter_wave) 60 | sprintf(fmt "%%.%de" precision) 61 | sprintf(line_fmt "%s\n" fmt) 62 | wave_cls = className(classOf(wave)) 63 | 64 | if( not( entry = assoc( var_name cdar(data_list_struct) ) ) then 65 | ; first time saving this variable 66 | sprintf(data_file "%s/%s.data" directory var_name) 67 | sprintf(sweep_file "%s/%s.sweep" directory var_name) 68 | cond( 69 | ( or( drIsWaveform(wave) drIsParamWave(wave) ) 70 | ; save sweep names 71 | fhandle = outfile( sweep_file "w" ) 72 | name_list = sweepNames(wave) 73 | foreach(swp_name name_list 74 | fprintf(fhandle "%s\n" swp_name) 75 | ) 76 | close(fhandle) 77 | 78 | ; save sweep values 79 | iter_wave = wave 80 | foreach(swp_name name_list 81 | ; save output most sweep values 82 | val_list = sweepValues(iter_wave) 83 | sprintf(sweep_df "%s/%s.info" directory swp_name) 84 | unless( isFile(sweep_df) 85 | fhandle = outfile( sweep_df "w" ) 86 | foreach(val val_list 87 | fprintf(fhandle line_fmt val) 88 | ) 89 | close(fhandle) 90 | ) 91 | ; remove outer sweep 92 | when( drIsParamWave(iter_wave) 93 | iter_wave = famValue(iter_wave car(val_list)) 94 | ) 95 | ) 96 | 97 | fhandle = outfile( data_file "w" ) 98 | ) 99 | ( or( wave_cls == 'flonum wave_cls == 'fixnum wave_cls == 'adtComplex ) 100 | ; scalar data, make empty sweep file 101 | fhandle = outfile( sweep_file "w") 102 | close(fhandle) 103 | fhandle = outfile( data_file "w" ) 104 | ) 105 | ( t 106 | ; unsupported type 107 | error("Unsupported data for output %s: %A\n" var_name wave) 108 | ) 109 | ) 110 | tconc( data_list_struct list(var_name fhandle) ) 111 | else 112 | fhandle = cadr(entry) 113 | ) 114 | 115 | ; append data to file 116 | if( or( drIsWaveform(wave) drIsParamWave(wave) ) then 117 | save_param_wave_values(wave fmt line_fmt fhandle) 118 | else 119 | ; print single point value 120 | if( wave_cls == 'adtComplex then 121 | ; print complex 122 | if( imag(wave) < 0 then 123 | ; fix for negative imaginary part. 124 | sprintf(line_fmt "%s%sj\n" fmt fmt) 125 | else 126 | sprintf(line_fmt "%s+%sj\n" fmt fmt) 127 | ) 128 | fprintf(fhandle line_fmt real(wave) imag(wave)) 129 | else 130 | fprintf(fhandle line_fmt wave) 131 | ) 132 | ) 133 | 't 134 | ) 135 | ) 136 | 137 | ocnSetXLMode() 138 | ocnxlTargetCellView(lib cell view) 139 | ocnxlLoadSetupState(state 'overwrite) 140 | ocnxlHistoryPrefix(sim_tag) 141 | ocnxlJobSetup(job_opt_list) 142 | printf("*Info* Creating netlist...\n") 143 | createNetlist( ?recreateAll t ?display nil ) 144 | printf("*Info* Starting simulation...\n") 145 | ocnxlRun(?mode 'sweepAndCorners ?nominalCornerEnabled nil ?allCornersEnabled 't 146 | ?allSweepsEnabled 't) 147 | 148 | ; load result database 149 | hist_name = ocnxlGetCurrentHistory() 150 | rdb = axlReadHistoryResDB(hist_name) 151 | point_list = rdb->points() 152 | 153 | sprintf(sweep_fname "%s/sweep.info" save_dir) 154 | sweep_f = outfile( sweep_fname "w" ) 155 | 156 | ; write sweep parameters title 157 | when( point_list 158 | point = car(point_list) 159 | test_list = point->tests() 160 | when( test_list 161 | corner = car(test_list)->cornerName 162 | par_names = setof( name point->params(?corner corner ?sortBy 'name)~>name 163 | and( (name != "corModelSpec") (name != "temperature") ) ) 164 | 165 | fprintf(sweep_f "corner ") 166 | fprintf(sweep_f "%s\n" buildString( par_names " " )) 167 | ) 168 | ) 169 | 170 | ; iterate through each design point and save data. 171 | data_list_struct = tconc(nil 0) 172 | total_points = length(point_list) 173 | cur_idx = 1 174 | foreach(point point_list 175 | printf("*Info* saving process: %d/%d\n" cur_idx total_points) 176 | cur_idx = cur_idx + 1 177 | foreach(test point->tests() 178 | ; write param values to file. 179 | corner = test->cornerName 180 | params = setof(par point->params(?corner corner ?sortBy 'name) 181 | and( (par->name != "corModelSpec") (par->name != "temperature") ) ) 182 | param_vals = mapcar( lambda( (par) par->valueAsString(?digits precision ?notation 'eng) ) params ) 183 | fprintf(sweep_f "%s " corner) 184 | fprintf(sweep_f "%s\n" buildString( param_vals " " )) 185 | 186 | ; open results 187 | openResults(test->resultsDir) 188 | 189 | {% for var, expr in outputs.items() %} 190 | tmp = {{ expr }} 191 | save_waveform( save_dir "{{ var }}" tmp precision data_list_struct ) 192 | {% endfor %} 193 | 194 | ) 195 | ) 196 | 197 | ; close opened files 198 | close(sweep_f) 199 | foreach( entry cdar(data_list_struct) 200 | close(cadr(entry)) 201 | ) 202 | 203 | ocnxlEndXLMode() 204 | 205 | exit() 206 | -------------------------------------------------------------------------------- /bag/io/gui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | import json 7 | import select 8 | 9 | import PyQt5.QtWidgets as QtWidgets 10 | import PyQt5.QtCore as QtCore 11 | 12 | from .file import write_file, open_file 13 | from .common import to_bytes 14 | 15 | if os.name != 'posix': 16 | raise Exception('bag.io.gui module current only works for POSIX systems.') 17 | 18 | 19 | class StdinThread(QtCore.QThread): 20 | """A QT worker thread that reads stdin.""" 21 | update = QtCore.pyqtSignal('QString') 22 | 23 | def __init__(self, parent): 24 | QtCore.QThread.__init__(self, parent=parent) 25 | self.stop = False 26 | 27 | def run(self): 28 | while not self.stop: 29 | try: 30 | stdin, _, _ = select.select([sys.stdin], [], [], 0.05) 31 | if stdin: 32 | cmd = sys.stdin.readline().strip() 33 | else: 34 | cmd = None 35 | except: 36 | cmd = 'exit' 37 | 38 | if cmd is not None: 39 | self.stop = (cmd == 'exit') 40 | self.update.emit(cmd) 41 | 42 | 43 | class LogWidget(QtWidgets.QFrame): 44 | """A Logger window widget. 45 | 46 | Note: due to QPlainTextEdit always adding an extra newline when calling 47 | appendPlainText(), we keep track of internal buffer and only print output 48 | one line at a time. This may cause some message to not display immediately. 49 | """ 50 | 51 | def __init__(self, parent=None): 52 | QtWidgets.QFrame.__init__(self, parent=parent) 53 | 54 | self.logger = QtWidgets.QPlainTextEdit(parent=self) 55 | self.logger.setReadOnly(True) 56 | self.logger.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) 57 | self.logger.setMinimumWidth(1100) 58 | self.buffer = '' 59 | 60 | self.clear_button = QtWidgets.QPushButton('Clear Log', parent=self) 61 | self.clear_button.clicked.connect(self.clear_log) 62 | self.save_button = QtWidgets.QPushButton('Save Log As...', parent=self) 63 | self.save_button.clicked.connect(self.save_log) 64 | 65 | self.lay = QtWidgets.QVBoxLayout(self) 66 | self.lay.addWidget(self.logger) 67 | self.lay.addWidget(self.clear_button) 68 | self.lay.addWidget(self.save_button) 69 | 70 | def clear_log(self): 71 | self.logger.setPlainText('') 72 | self.buffer = '' 73 | 74 | def save_log(self): 75 | root_dir = os.getcwd() 76 | fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File', root_dir) 77 | if fname: 78 | write_file(fname, self.logger.toPlainText() + '\n') 79 | 80 | def print_file(self, file_obj): 81 | # this code converts all types of newlines (such as '\r\n') to '\n', 82 | # and make sure any ending newlines are preserved. 83 | for line in file_obj: 84 | if self.buffer: 85 | line = self.buffer + line 86 | self.buffer = '' 87 | if line.endswith('\n'): 88 | self.logger.appendPlainText(line[:-1]) 89 | else: 90 | self.buffer = line 91 | 92 | 93 | class LogViewer(QtWidgets.QWidget): 94 | """A Simple window to see process log in real time..""" 95 | 96 | def __init__(self): 97 | QtWidgets.QWidget.__init__(self) 98 | 99 | # combo box label 100 | self.label = QtWidgets.QLabel('Log File: ', parent=self) 101 | # populate log selection combo box. 102 | self.combo_box = QtWidgets.QComboBox(parent=self) 103 | self.log_files = [] 104 | self.reader = None 105 | 106 | self.logger = LogWidget(parent=self) 107 | 108 | # setup GUI 109 | self.setWindowTitle('BAG Simulation Log Viewer') 110 | self.setAttribute(QtCore.Qt.WA_DeleteOnClose) 111 | 112 | self.layout = QtWidgets.QGridLayout(self) 113 | self.layout.addWidget(self.label, 0, 0, alignment=QtCore.Qt.AlignRight) 114 | self.layout.addWidget(self.combo_box, 0, 1, alignment=QtCore.Qt.AlignLeft) 115 | self.layout.addWidget(self.logger, 1, 0, -1, -1) 116 | self.layout.setRowStretch(0, 0.0) 117 | self.layout.setRowStretch(1, 1.0) 118 | self.layout.setColumnStretch(0, 0.0) 119 | self.layout.setColumnStretch(1, 0.0) 120 | 121 | # setup file watcher 122 | self.cur_paths = None 123 | self.watcher = QtCore.QFileSystemWatcher(parent=self) 124 | # setup signals 125 | self.watcher.fileChanged.connect(self.update_logfile) 126 | self.combo_box.currentIndexChanged.connect(self.change_log) 127 | 128 | # start thread 129 | self.thread = StdinThread(self) 130 | self.thread.update.connect(self.parse_cmd) 131 | self.thread.start() 132 | 133 | def closeEvent(self, evt): 134 | if not self.thread.stop: 135 | self.thread.stop = True 136 | self.thread.wait() 137 | QtWidgets.QWidget.closeEvent(self, evt) 138 | 139 | @QtCore.pyqtSlot('QString') 140 | def parse_cmd(self, cmd): 141 | if cmd == 'exit': 142 | self.close() 143 | else: 144 | try: 145 | cmd = json.loads(cmd) 146 | if cmd[0] == 'add': 147 | self.add_log(cmd[1], cmd[2]) 148 | elif cmd[0] == 'remove': 149 | self.remove_log(cmd[1]) 150 | except: 151 | pass 152 | 153 | @QtCore.pyqtSlot('int') 154 | def change_log(self, new_idx): 155 | # print('log change called, switching to index %d' % new_idx) 156 | if self.cur_paths is not None: 157 | self.watcher.removePaths(self.cur_paths) 158 | self.logger.clear_log() 159 | if self.reader is not None: 160 | self.reader.close() 161 | self.reader = None 162 | 163 | if new_idx >= 0: 164 | fname = os.path.abspath(self.log_files[new_idx]) 165 | dname = os.path.dirname(fname) 166 | self.reader = open_file(fname, 'r') 167 | self.logger.print_file(self.reader) 168 | self.cur_paths = [dname, fname] 169 | self.watcher.addPaths(self.cur_paths) 170 | 171 | @QtCore.pyqtSlot('QString') 172 | def update_logfile(self, fname): 173 | # print('filechanged called, fname = %s' % fname) 174 | if self.reader is not None: 175 | self.logger.print_file(self.reader) 176 | 177 | def remove_log(self, log_tag): 178 | idx = self.combo_box.findText(log_tag) 179 | if idx >= 0: 180 | del self.log_files[idx] 181 | self.combo_box.removeItem(idx) 182 | 183 | def add_log(self, log_tag, log_file): 184 | self.remove_log(log_tag) 185 | if os.path.isfile(log_file): 186 | self.log_files.append(log_file) 187 | self.combo_box.addItem(log_tag) 188 | 189 | 190 | def app_start(): 191 | app = QtWidgets.QApplication([]) 192 | 193 | window = LogViewer() 194 | app.window_reference = window 195 | window.show() 196 | app.exec_() 197 | 198 | 199 | def start_viewer(): 200 | cmd = [sys.executable, '-m', 'bag.io.gui'] 201 | devnull = open(os.devnull, 'w') 202 | proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=devnull, 203 | stderr=subprocess.STDOUT, 204 | preexec_fn=os.setpgrp) 205 | return proc 206 | 207 | 208 | def add_log(proc, tag, fname): 209 | if proc is not None: 210 | if proc.poll() is not None or proc.stdin.closed: 211 | # process finished 212 | return False 213 | cmd_str = json.dumps(['add', tag, fname]) + '\n' 214 | proc.stdin.write(to_bytes(cmd_str)) 215 | proc.stdin.flush() 216 | return True 217 | 218 | 219 | def remove_log(proc, tag): 220 | if proc is not None: 221 | if proc.poll() is not None or proc.stdin.closed: 222 | # process finished 223 | return False 224 | cmd_str = json.dumps(['remove', tag]) + '\n' 225 | proc.stdin.write(to_bytes(cmd_str)) 226 | proc.stdin.flush() 227 | return True 228 | 229 | 230 | def close(proc): 231 | if proc is not None and proc.poll() is None: 232 | proc.stdin.close() 233 | 234 | if __name__ == '__main__': 235 | app_start() 236 | -------------------------------------------------------------------------------- /bag/interface/simulator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module handles high level simulation routines. 4 | 5 | This module defines SimAccess, which provides methods to run simulations 6 | and retrieve results. 7 | """ 8 | 9 | from typing import Dict, Optional, Sequence, Any, Tuple, Union 10 | 11 | import abc 12 | 13 | from ..io import make_temp_dir 14 | from ..concurrent.core import SubProcessManager 15 | from .base import InterfaceBase 16 | 17 | 18 | class SimAccess(InterfaceBase, abc.ABC): 19 | """A class that interacts with a simulator. 20 | 21 | Parameters 22 | ---------- 23 | tmp_dir : str 24 | temporary file directory for SimAccess. 25 | sim_config : Dict[str, Any] 26 | the simulation configuration dictionary. 27 | """ 28 | 29 | def __init__(self, tmp_dir, sim_config): 30 | # type: (str, Dict[str, Any]) -> None 31 | InterfaceBase.__init__(self) 32 | 33 | self.sim_config = sim_config 34 | self.tmp_dir = make_temp_dir('simTmp', parent_dir=tmp_dir) 35 | 36 | @abc.abstractmethod 37 | def format_parameter_value(self, param_config, precision): 38 | # type: (Dict[str, Any], int) -> str 39 | """Format the given parameter value as a string. 40 | 41 | To support both single value parameter and parameter sweeps, each parameter value is represented 42 | as a string instead of simple floats. This method will cast a parameter configuration (which can 43 | either be a single value or a sweep) to a simulator-specific string. 44 | 45 | Parameters 46 | ---------- 47 | param_config: Dict[str, Any] 48 | a dictionary that describes this parameter value. 49 | 50 | 4 formats are supported. This is best explained by example. 51 | 52 | single value: 53 | dict(type='single', value=1.0) 54 | 55 | sweep a given list of values: 56 | dict(type='list', values=[1.0, 2.0, 3.0]) 57 | 58 | linear sweep with inclusive start, inclusive stop, and step size: 59 | dict(type='linstep', start=1.0, stop=3.0, step=1.0) 60 | 61 | logarithmic sweep with given number of points per decade: 62 | dict(type='decade', start=1.0, stop=10.0, num=10) 63 | 64 | precision : int 65 | the parameter value precision. 66 | 67 | Returns 68 | ------- 69 | param_str : str 70 | a string representation of param_config 71 | """ 72 | return "" 73 | 74 | @abc.abstractmethod 75 | async def async_run_simulation(self, tb_lib, tb_cell, outputs, precision=6, sim_tag=None): 76 | # type: (str, str, Dict[str, str], int, Optional[str]) -> str 77 | """A coroutine for simulation a testbench. 78 | 79 | Parameters 80 | ---------- 81 | tb_lib : str 82 | testbench library name. 83 | tb_cell : str 84 | testbench cell name. 85 | outputs : Dict[str, str] 86 | the variable-to-expression dictionary. 87 | precision : int 88 | precision of floating point results. 89 | sim_tag : Optional[str] 90 | a descriptive tag describing this simulation run. 91 | 92 | Returns 93 | ------- 94 | value : str 95 | the save directory path. 96 | """ 97 | pass 98 | 99 | @abc.abstractmethod 100 | async def async_load_results(self, lib, cell, hist_name, outputs, precision=6): 101 | # type: (str, str, str, Dict[str, str], int) -> str 102 | """A coroutine for loading simulation results. 103 | 104 | Parameters 105 | ---------- 106 | lib : str 107 | testbench library name. 108 | cell : str 109 | testbench cell name. 110 | hist_name : str 111 | simulation history name. 112 | outputs : Dict[str, str] 113 | the variable-to-expression dictionary. 114 | precision : int 115 | precision of floating point results. 116 | 117 | Returns 118 | ------- 119 | value : str 120 | the save directory path. 121 | """ 122 | pass 123 | 124 | 125 | ProcInfo = Tuple[Union[str, Sequence[str]], str, Optional[Dict[str, str]], Optional[str], str] 126 | 127 | 128 | class SimProcessManager(SimAccess, metaclass=abc.ABCMeta): 129 | """An implementation of :class:`SimAccess` using :class:`SubProcessManager`. 130 | 131 | Parameters 132 | ---------- 133 | tmp_dir : str 134 | temporary file directory for SimAccess. 135 | sim_config : Dict[str, Any] 136 | the simulation configuration dictionary. 137 | """ 138 | 139 | def __init__(self, tmp_dir, sim_config): 140 | # type: (str, Dict[str, Any]) -> None 141 | SimAccess.__init__(self, tmp_dir, sim_config) 142 | cancel_timeout = sim_config.get('cancel_timeout_ms', None) 143 | if cancel_timeout is not None: 144 | cancel_timeout /= 1e3 145 | self._manager = SubProcessManager(max_workers=sim_config.get('max_workers', None), 146 | cancel_timeout=cancel_timeout) 147 | 148 | @abc.abstractmethod 149 | def setup_sim_process(self, lib, cell, outputs, precision, sim_tag): 150 | # type: (str, str, Dict[str, str], int, Optional[str]) -> ProcInfo 151 | """This method performs any setup necessary to configure a simulation process. 152 | 153 | Parameters 154 | ---------- 155 | lib : str 156 | testbench library name. 157 | cell : str 158 | testbench cell name. 159 | outputs : Dict[str, str] 160 | the variable-to-expression dictionary. 161 | precision : int 162 | precision of floating point results. 163 | sim_tag : Optional[str] 164 | a descriptive tag describing this simulation run. 165 | 166 | Returns 167 | ------- 168 | args : Union[str, Sequence[str]] 169 | command to run, as string or list of string arguments. 170 | log : str 171 | log file name. 172 | env : Optional[Dict[str, str]] 173 | environment variable dictionary. None to inherit from parent. 174 | cwd : Optional[str] 175 | working directory path. None to inherit from parent. 176 | save_dir : str 177 | save directory path. 178 | """ 179 | return '', '', None, None, '' 180 | 181 | @abc.abstractmethod 182 | def setup_load_process(self, lib, cell, hist_name, outputs, precision): 183 | # type: (str, str, str, Dict[str, str], int) -> ProcInfo 184 | """This method performs any setup necessary to configure a result loading process. 185 | 186 | Parameters 187 | ---------- 188 | lib : str 189 | testbench library name. 190 | cell : str 191 | testbench cell name. 192 | hist_name : str 193 | simulation history name. 194 | outputs : Dict[str, str] 195 | the variable-to-expression dictionary. 196 | precision : int 197 | precision of floating point results. 198 | 199 | Returns 200 | ------- 201 | args : Union[str, Sequence[str]] 202 | command to run, as string or list of string arguments. 203 | log : str 204 | log file name. 205 | env : Optional[Dict[str, str]] 206 | environment variable dictionary. None to inherit from parent. 207 | cwd : Optional[str] 208 | working directory path. None to inherit from parent. 209 | save_dir : str 210 | save directory path. 211 | """ 212 | return '', '', None, None, '' 213 | 214 | async def async_run_simulation(self, tb_lib: str, tb_cell: str, 215 | outputs: Dict[str, str], 216 | precision: int = 6, 217 | sim_tag: Optional[str] = None) -> str: 218 | args, log, env, cwd, save_dir = self.setup_sim_process(tb_lib, tb_cell, outputs, precision, 219 | sim_tag) 220 | 221 | await self._manager.async_new_subprocess(args, log, env=env, cwd=cwd) 222 | return save_dir 223 | 224 | async def async_load_results(self, lib: str, cell: str, hist_name: str, 225 | outputs: Dict[str, str], 226 | precision: int = 6) -> str: 227 | args, log, env, cwd, save_dir = self.setup_load_process(lib, cell, hist_name, outputs, 228 | precision) 229 | 230 | await self._manager.async_new_subprocess(args, log, env=env, cwd=cwd) 231 | return save_dir 232 | -------------------------------------------------------------------------------- /bag/data/digital.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines functions useful for digital verification/postprocessing. 4 | """ 5 | 6 | from typing import Optional, List, Tuple 7 | 8 | import numpy as np 9 | 10 | from .core import Waveform 11 | 12 | 13 | def de_bruijn(n, symbols=None): 14 | # type: (int, Optional[List[float]]) -> List[float] 15 | """Returns a De Bruijn sequence with subsequence of length n. 16 | 17 | a De Bruijn sequence with subsequence of length n is a sequence such that 18 | all possible subsequences of length n appear exactly once somewhere in the 19 | sequence. This method is useful for simulating the worst case eye diagram 20 | given finite impulse response. 21 | 22 | Parameters 23 | ---------- 24 | n : int 25 | length of the subsequence. 26 | symbols : Optional[List[float]] or None 27 | the list of symbols. If None, defaults to [0.0, 1.0]. 28 | 29 | Returns 30 | ------- 31 | seq : List[float] 32 | the de bruijn sequence. 33 | """ 34 | symbols = symbols or [0.0, 1.0] 35 | k = len(symbols) 36 | 37 | a = [0] * (k * n) 38 | sequence = [] 39 | 40 | def db(t, p): 41 | if t > n: 42 | if n % p == 0: 43 | sequence.extend(a[1:p + 1]) 44 | else: 45 | a[t] = a[t - p] 46 | db(t + 1, p) 47 | for j in range(a[t - p] + 1, k): 48 | a[t] = j 49 | db(t + 1, t) 50 | 51 | db(1, 1) 52 | return [symbols[i] for i in sequence] 53 | 54 | 55 | def dig_to_pwl(values, tper, trf, td=0): 56 | # type: (List[float], float, float, float) -> Tuple[List[float], List[float]] 57 | """Convert a list of digital bits to PWL waveform. 58 | 59 | This function supports negative delay. However, time/value pairs for negative data 60 | are truncated. 61 | 62 | Parameters 63 | ---------- 64 | values : List[float] 65 | list of values for each bit. 66 | tper : float 67 | the period in seconds. 68 | trf : float 69 | the rise/fall time in seconds. 70 | td : float 71 | the delay 72 | 73 | Returns 74 | ------- 75 | tvec : List[float] 76 | the time vector. 77 | yvec : List[float] 78 | the value vector. 79 | """ 80 | y0 = values[0] 81 | tcur, ycur = td, y0 82 | tvec, yvec = [], [] 83 | for v in values: 84 | if v != ycur: 85 | if tcur >= 0: 86 | tvec.append(tcur) 87 | yvec.append(ycur) 88 | elif tcur < 0 < tcur + trf: 89 | # make sure time starts at 0 90 | tvec.append(0) 91 | yvec.append(ycur - (v - ycur) / trf * tcur) 92 | ycur = v 93 | if tcur + trf >= 0: 94 | tvec.append(tcur + trf) 95 | yvec.append(ycur) 96 | elif tcur + trf < 0 < tcur + tper: 97 | # make sure time starts at 0 98 | tvec.append(0) 99 | yvec.append(ycur) 100 | tcur += tper 101 | else: 102 | if tcur <= 0 < tcur + tper: 103 | # make sure time starts at 0 104 | tvec.append(0) 105 | yvec.append(ycur) 106 | tcur += tper 107 | 108 | if not tvec: 109 | # only here if input is constant 110 | tvec = [0, tper] 111 | yvec = [y0, y0] 112 | elif tvec[0] > 0: 113 | # make time start at 0 114 | tvec.insert(0, 0) 115 | yvec.insert(0, y0) 116 | 117 | return tvec, yvec 118 | 119 | 120 | def get_crossing_index(yvec, threshold, n=0, rising=True): 121 | # type: (np.array, float, int, bool) -> int 122 | """Returns the first index that the given numpy array crosses the given threshold. 123 | 124 | Parameters 125 | ---------- 126 | yvec : np.array 127 | the numpy array. 128 | threshold : float 129 | the crossing threshold. 130 | n : int 131 | returns the nth edge index, with n=0 being the first index. 132 | rising : bool 133 | True to return rising edge index. False to return falling edge index. 134 | 135 | Returns 136 | ------- 137 | idx : int 138 | the crossing edge index. 139 | """ 140 | 141 | bool_vec = yvec >= threshold 142 | qvec = bool_vec.astype(int) 143 | dvec = np.diff(qvec) 144 | 145 | dvec = np.maximum(dvec, 0) if rising else np.minimum(dvec, 0) 146 | idx_list = dvec.nonzero()[0] 147 | return idx_list[n] 148 | 149 | 150 | def get_flop_timing(tvec, d, q, clk, ttol, data_thres=0.5, 151 | clk_thres=0.5, tstart=0.0, clk_edge='rising', tag=None, invert=False): 152 | """Calculate flop timing parameters given the associated waveforms. 153 | 154 | This function performs the following steps: 155 | 156 | 1. find all valid clock edges. Compute period of the clock (clock waveform 157 | must be periodic). 158 | 159 | 2. For each valid clock edge: 160 | 161 | A. Check if the input changes in the previous cycle. If so, compute tsetup. 162 | Otherwise, tsetup = tperiod. 163 | 164 | B. Check if input changes in the current cycle. If so, compute thold. 165 | Otherwise, thold = tperiod. 166 | 167 | C. Check that output transition at most once and that output = input. 168 | Otherwise, record an error. 169 | 170 | D. record the output data polarity. 171 | 172 | 3. For each output data polarity, compute the minimum tsetup and thold and any 173 | errors. Return summary as a dictionary. 174 | 175 | 176 | The output is a dictionary with keys 'setup', 'hold', 'delay', and 'errors'. 177 | the setup/hold/delay entries contains 2-element tuples describing the worst 178 | setup/hold/delay time. The first element is the setup/hold/delay time, and 179 | the second element is the clock edge time at which it occurs. The errors field 180 | stores all clock edge times at which an error occurs. 181 | 182 | 183 | Parameters 184 | ---------- 185 | tvec : np.ndarray 186 | the time data. 187 | d : np.ndarray 188 | the input data. 189 | q : np.ndarray 190 | the output data. 191 | clk : np.ndarray 192 | the clock data. 193 | ttol : float 194 | time resolution. 195 | data_thres : float 196 | the data threshold. 197 | clk_thres : float 198 | the clock threshold. 199 | tstart : float 200 | ignore data points before tstart. 201 | clk_edge : str 202 | the clock edge type. Valid values are "rising", "falling", or "both". 203 | tag : obj 204 | an identifier tag to append to results. 205 | invert : bool 206 | if True, the flop output is inverted from the data. 207 | 208 | Returns 209 | ------- 210 | data : dict[str, any] 211 | A dictionary describing the worst setup/hold/delay and errors, if any. 212 | """ 213 | d_wv = Waveform(tvec, d, ttol) 214 | clk_wv = Waveform(tvec, clk, ttol) 215 | q_wv = Waveform(tvec, q, ttol) 216 | tend = tvec[-1] 217 | 218 | # get all clock sampling times and clock period 219 | samp_times = clk_wv.get_all_crossings(clk_thres, start=tstart, edge=clk_edge) 220 | tper = (samp_times[-1] - samp_times[0]) / (len(samp_times) - 1) 221 | # ignore last clock cycle if it's not a full cycle. 222 | if samp_times[-1] + tper > tend: 223 | samp_times = samp_times[:-1] 224 | 225 | # compute setup/hold/error for each clock period 226 | data = {'setup': (tper, -1), 'hold': (tper, -1), 'delay': (0.0, -1), 'errors': []} 227 | for t in samp_times: 228 | d_prev = d_wv.get_all_crossings(data_thres, start=t - tper, stop=t, edge='both') 229 | d_cur = d_wv.get_all_crossings(data_thres, start=t, stop=t + tper, edge='both') 230 | q_cur = q_wv.get_all_crossings(data_thres, start=t, stop=t + tper, edge='both') 231 | d_val = d_wv(t) > data_thres 232 | q_val = q_wv(t + tper) > data_thres 233 | 234 | # calculate setup/hold/delay 235 | tsetup = t - d_prev[-1] if d_prev else tper 236 | thold = d_cur[0] - t if d_cur else tper 237 | tdelay = q_cur[0] - t if q_cur else 0.0 238 | 239 | # check if flop has error 240 | error = (invert != (q_val != d_val)) or (len(q_cur) > 1) 241 | 242 | # record results 243 | if tsetup < data['setup'][0]: 244 | data['setup'] = (tsetup, t) 245 | if thold < data['hold'][0]: 246 | data['hold'] = (thold, t) 247 | if tdelay > data['delay'][0]: 248 | data['delay'] = (tdelay, t) 249 | if error: 250 | data['errors'].append(t) 251 | 252 | if tag is not None: 253 | data['setup'] += (tag, ) 254 | data['hold'] += (tag, ) 255 | data['delay'] += (tag, ) 256 | data['errors'] = [(t, tag) for t in data['errors']] 257 | 258 | return data 259 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BAG.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BAG.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/BAG" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BAG" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /bag/data/dc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines classes for computing DC operating point. 4 | """ 5 | 6 | from typing import Union, Dict 7 | 8 | import scipy.sparse 9 | import scipy.optimize 10 | import numpy as np 11 | 12 | from bag.tech.mos import MosCharDB 13 | 14 | 15 | class DCCircuit(object): 16 | """A class that solves DC operating point of a circuit. 17 | 18 | Parameters 19 | ---------- 20 | ndb : MosCharDB 21 | nmos characterization database. 22 | pdb : MosCharDB 23 | pmos characterization database. 24 | """ 25 | 26 | def __init__(self, ndb, pdb): 27 | # type: (MosCharDB, MosCharDB) -> None 28 | self._n = 1 29 | self._ndb = ndb 30 | self._pdb = pdb 31 | self._transistors = {} 32 | self._node_id = {'gnd': 0, 'vss': 0, 'VSS': 0} 33 | self._node_name_lookup = {0: 'gnd'} 34 | self._node_voltage = {0: 0} 35 | 36 | def _get_node_id(self, name): 37 | # type: (str) -> int 38 | if name not in self._node_id: 39 | ans = self._n 40 | self._node_id[name] = ans 41 | self._node_name_lookup[ans] = name 42 | self._n += 1 43 | return ans 44 | else: 45 | return self._node_id[name] 46 | 47 | def set_voltage_source(self, node_name, voltage): 48 | # type: (str, float) -> None 49 | """ 50 | Specify voltage the a node. 51 | 52 | Parameters 53 | ---------- 54 | node_name : str 55 | the net name. 56 | voltage : float 57 | voltage of the given net. 58 | """ 59 | node_id = self._get_node_id(node_name) 60 | self._node_voltage[node_id] = voltage 61 | 62 | def add_transistor(self, d_name, g_name, s_name, b_name, mos_type, intent, w, lch, fg=1): 63 | # type: (str, str, str, str, str, str, Union[float, int], float, int) -> None 64 | """Adds a small signal transistor model to the circuit. 65 | 66 | Parameters 67 | ---------- 68 | d_name : str 69 | drain net name. 70 | g_name : str 71 | gate net name. 72 | s_name : str 73 | source net name. 74 | b_name : str 75 | body net name. Defaults to 'gnd'. 76 | mos_type : str 77 | transistor type. Either 'nch' or 'pch'. 78 | intent : str 79 | transistor threshold flavor. 80 | w : Union[float, int] 81 | transistor width. 82 | lch : float 83 | transistor channel length. 84 | fg : int 85 | transistor number of fingers. 86 | """ 87 | node_d = self._get_node_id(d_name) 88 | node_g = self._get_node_id(g_name) 89 | node_s = self._get_node_id(s_name) 90 | node_b = self._get_node_id(b_name) 91 | 92 | # get existing current function. Initalize if not found. 93 | ids_key = (mos_type, intent, lch) 94 | if ids_key in self._transistors: 95 | arow, acol, bdata, fg_list, ds_list = self._transistors[ids_key] 96 | else: 97 | arow, acol, bdata, fg_list, ds_list = [], [], [], [], [] 98 | self._transistors[ids_key] = (arow, acol, bdata, fg_list, ds_list) 99 | 100 | # record Ai and bi data 101 | offset = len(fg_list) * 4 102 | arow.extend([offset + 1, offset + 1, offset + 2, offset + 2, offset + 3, offset + 3]) 103 | acol.extend([node_b, node_s, node_d, node_s, node_g, node_s]) 104 | bdata.append(w) 105 | fg_list.append(fg) 106 | ds_list.append((node_d, node_s)) 107 | 108 | def solve(self, env, guess_dict, itol=1e-10, inorm=1e-6): 109 | # type: (str, Dict[str, float], float, float) -> Dict[str, float] 110 | """Solve DC operating point. 111 | 112 | Parameters 113 | ---------- 114 | env : str 115 | the simulation environment. 116 | guess_dict : Dict[str, float] 117 | initial guess dictionary. 118 | itol : float 119 | current error tolerance. 120 | inorm : float 121 | current normalization factor. 122 | 123 | Returns 124 | ------- 125 | op_dict : Dict[str, float] 126 | DC operating point dictionary. 127 | """ 128 | # step 1: get list of nodes to solve 129 | node_list = [idx for idx in range(self._n) if idx not in self._node_voltage] 130 | reverse_dict = {nid: idx for idx, nid in enumerate(node_list)} 131 | ndim = len(node_list) 132 | 133 | # step 2: get Av and bv 134 | amatv = scipy.sparse.csr_matrix(([1] * ndim, (node_list, np.arange(ndim))), shape=(self._n, ndim)) 135 | bmatv = np.zeros(self._n) 136 | for nid, val in self._node_voltage.items(): 137 | bmatv[nid] = val 138 | 139 | # step 3: gather current functions, and output matrix entries 140 | ifun_list = [] 141 | out_data = [] 142 | out_row = [] 143 | out_col = [] 144 | out_col_cnt = 0 145 | for (mos_type, intent, lch), (arow, acol, bdata, fg_list, ds_list) in self._transistors.items(): 146 | db = self._ndb if mos_type == 'nch' else self._pdb 147 | ifun = db.get_function('ids', env=env, intent=intent, l=lch) 148 | # step 3A: compute Ai and bi 149 | num_tran = len(fg_list) 150 | adata = [1, -1] * (3 * num_tran) 151 | amati = scipy.sparse.csr_matrix((adata, (arow, acol)), shape=(4 * num_tran, self._n)) 152 | bmati = np.zeros(4 * num_tran) 153 | bmati[0::4] = bdata 154 | 155 | # step 3B: compute A = Ai * Av, b = Ai * bv + bi 156 | amat = amati.dot(amatv) 157 | bmat = amati.dot(bmatv) + bmati 158 | # record scale matrix and function. 159 | scale_mat = scipy.sparse.diags(fg_list) / inorm 160 | ifun_list.append((ifun, scale_mat, amat, bmat)) 161 | for node_d, node_s in ds_list: 162 | if node_d in reverse_dict: 163 | out_row.append(reverse_dict[node_d]) 164 | out_data.append(-1) 165 | out_col.append(out_col_cnt) 166 | if node_s in reverse_dict: 167 | out_row.append(reverse_dict[node_s]) 168 | out_data.append(1) 169 | out_col.append(out_col_cnt) 170 | out_col_cnt += 1 171 | # construct output matrix 172 | out_mat = scipy.sparse.csr_matrix((out_data, (out_row, out_col)), shape=(ndim, out_col_cnt)) 173 | 174 | # step 4: define zero function 175 | def zero_fun(varr): 176 | iarr = np.empty(out_col_cnt) 177 | offset = 0 178 | for idsf, smat, ai, bi in ifun_list: 179 | num_out = smat.shape[0] 180 | # reshape going row first instead of column 181 | arg = (ai.dot(varr) + bi).reshape(4, -1, order='F').T 182 | if idsf.ndim == 3: 183 | # handle case where transistor source and body are shorted 184 | tmpval = idsf(arg[:, [0, 2, 3]]) 185 | else: 186 | tmpval = idsf(arg) 187 | iarr[offset:offset + num_out] = smat.dot(tmpval) 188 | offset += num_out 189 | return out_mat.dot(iarr) 190 | 191 | # step 5: define zero function 192 | def jac_fun(varr): 193 | jarr = np.empty((out_col_cnt, ndim)) 194 | offset = 0 195 | for idsf, smat, ai, bi in ifun_list: 196 | num_out = smat.shape[0] 197 | # reshape going row first instead of column 198 | arg = (ai.dot(varr) + bi).reshape(4, -1, order='F').T 199 | if idsf.ndim == 3: 200 | # handle case where transistor source and body are shorted 201 | tmpval = idsf.jacobian(arg[:, [0, 2, 3]]) 202 | # noinspection PyTypeChecker 203 | tmpval = np.insert(tmpval, 1, 0.0, axis=len(tmpval.shape) - 1) 204 | else: 205 | tmpval = idsf.jacobian(arg) 206 | jcur = smat.dot(tmpval) 207 | for idx in range(num_out): 208 | # ai is sparse matrix; multiplication is matrix 209 | jarr[offset + idx, :] = jcur[idx, :] @ ai[4 * idx:4 * idx + 4, :] 210 | offset += num_out 211 | return out_mat.dot(jarr) 212 | 213 | xguess = np.empty(ndim) 214 | for name, guess_val in guess_dict.items(): 215 | xguess[reverse_dict[self._node_id[name]]] = guess_val 216 | 217 | result = scipy.optimize.root(zero_fun, xguess, jac=jac_fun, tol=itol / inorm, method='hybr') 218 | if not result.success: 219 | raise ValueError('solution failed.') 220 | 221 | op_dict = {self._node_name_lookup[nid]: result.x[idx] for idx, nid in enumerate(node_list)} 222 | return op_dict 223 | -------------------------------------------------------------------------------- /bag/io/sim_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module handles simulation data related IO. 4 | 5 | Note : when reading data files, we use Numpy to handle the encodings, 6 | so BAG encoding settings will not apply. 7 | """ 8 | 9 | import os 10 | import glob 11 | 12 | import numpy as np 13 | import h5py 14 | 15 | from .common import bag_encoding, bag_codec_error 16 | 17 | illegal_var_name = ['sweep_params'] 18 | 19 | 20 | class SweepArray(np.ndarray): 21 | """Subclass of numpy array that adds sweep parameters attribute. 22 | """ 23 | 24 | def __new__(cls, data, sweep_params=None): 25 | # Input array is an already formed ndarray instance 26 | # We first cast to be our class type 27 | obj = np.asarray(data).view(cls) 28 | # add the new attribute to the created instance 29 | obj.sweep_params = sweep_params 30 | # Finally, we must return the newly created object: 31 | return obj 32 | 33 | def __array_finalize__(self, obj): 34 | # see InfoArray.__array_finalize__ for comments 35 | if obj is None: 36 | return 37 | self.sweep_params = getattr(obj, 'sweep_params', None) 38 | 39 | def __reduce__(self): 40 | # Get the parent's __reduce__ tuple 41 | pickled_state = super(SweepArray, self).__reduce__() 42 | # Create our own tuple to pass to __setstate__ 43 | new_state = pickled_state[2] + (self.sweep_params,) 44 | # Return a tuple that replaces the parent's __setstate__ tuple with our own 45 | return pickled_state[0], pickled_state[1], new_state 46 | 47 | # noinspection PyMethodOverriding 48 | def __setstate__(self, state): 49 | self.sweep_params = state[-1] # Set the info attribute 50 | # Call the parent's __setstate__ with the other tuple elements. 51 | # noinspection PyArgumentList 52 | super(SweepArray, self).__setstate__(state[0:-1]) 53 | 54 | 55 | def _get_sweep_params(fname): 56 | """Parse the sweep information file and reverse engineer sweep parameters. 57 | 58 | Parameters 59 | ---------- 60 | fname : str 61 | the sweep information file name. 62 | 63 | Returns 64 | ------- 65 | swp_list : list[str] 66 | list of sweep parameter names. index 0 is the outer-most loop. 67 | values_list : list[list[float or str]] 68 | list of values list for each sweep parameter. 69 | """ 70 | mat = np.genfromtxt(fname, dtype=np.unicode_) 71 | header = mat[0, :] 72 | data = mat[1:, :] 73 | 74 | # eliminate same data 75 | idx_list = [] 76 | for idx in range(len(header)): 77 | bool_vec = data[:, idx] == data[0, idx] # type: np.ndarray 78 | if not np.all(bool_vec): 79 | idx_list.append(idx) 80 | 81 | header = header[idx_list] 82 | data = data[:, idx_list] 83 | # find the first index of last element of each column. 84 | last_first_idx = [np.where(data[:, idx] == data[-1, idx])[0][0] for idx in range(len(header))] 85 | # sort by first index of last element; the column where the last element 86 | # appears the earliest is the inner most loop. 87 | order_list = np.argsort(last_first_idx) # type: np.ndarray 88 | 89 | # get list of values 90 | values_list = [] 91 | skip_len = 1 92 | for idx in order_list: 93 | end_idx = last_first_idx[idx] + 1 94 | values = data[0:end_idx:skip_len, idx] 95 | if header[idx] != 'corner': 96 | values = values.astype(np.float) 97 | skip_len *= len(values) 98 | values_list.append(values) 99 | 100 | swp_list = header[order_list][::-1].tolist() 101 | values_list.reverse() 102 | return swp_list, values_list 103 | 104 | 105 | def load_sim_results(save_dir): 106 | """Load exported simulation results from the given directory. 107 | 108 | Parameters 109 | ---------- 110 | save_dir : str 111 | the save directory path. 112 | 113 | Returns 114 | ------- 115 | results : dict[str, any] 116 | the simulation data dictionary. 117 | 118 | most keys in result is either a sweep parameter or an output signal. 119 | the values are the corresponding data as a numpy array. In addition, 120 | results has a key called 'sweep_params', which contains a dictionary from 121 | output signal name to a list of sweep parameters of that output. 122 | 123 | """ 124 | if not save_dir: 125 | return None 126 | 127 | results = {} 128 | sweep_params = {} 129 | 130 | # load sweep parameter values 131 | top_swp_list, values_list = _get_sweep_params(os.path.join(save_dir, 'sweep.info')) 132 | top_shape = [] 133 | for swp, values in zip(top_swp_list, values_list): 134 | results[swp] = values 135 | top_shape.append(len(values)) 136 | 137 | for swp_name in glob.glob(os.path.join(save_dir, '*.sweep')): 138 | base_name = os.path.basename(swp_name).split('.')[0] 139 | data_name = os.path.join(save_dir, '%s.data' % base_name) 140 | try: 141 | data_arr = np.loadtxt(data_name) 142 | except ValueError: 143 | # try loading complex 144 | data_arr = np.loadtxt(data_name, dtype=complex) 145 | 146 | # get sweep parameter names 147 | with open(swp_name, 'r', encoding='utf-8') as f: 148 | swp_list = [str(line.strip()) for line in f] 149 | 150 | # make a copy of master sweep list and sweep shape 151 | cur_swp_list = list(top_swp_list) 152 | cur_shape = list(top_shape) 153 | 154 | for swp in swp_list: 155 | if swp not in results: 156 | fname = os.path.join(save_dir, '%s.info' % swp) 157 | results[swp] = np.loadtxt(fname) 158 | 159 | # if sweep has more than one element. 160 | if results[swp].shape: 161 | cur_swp_list.append(swp) 162 | cur_shape.append(results[swp].shape[0]) 163 | 164 | # sanity check 165 | if base_name in results: 166 | raise Exception('Error: output named %s already in results' % base_name) 167 | 168 | # reshape data array 169 | data_arr = data_arr.reshape(cur_shape) 170 | results[base_name] = SweepArray(data_arr, cur_swp_list) 171 | # record sweep parameters for this data 172 | sweep_params[base_name] = cur_swp_list 173 | 174 | if 'sweep_params' in results: 175 | raise Exception('illegal output name: sweep_params') 176 | 177 | results['sweep_params'] = sweep_params 178 | 179 | return results 180 | 181 | 182 | def save_sim_results(results, fname, compression='gzip'): 183 | """Saves the given simulation results dictionary as a HDF5 file. 184 | 185 | Parameters 186 | ---------- 187 | results : dict[string, any] 188 | the results dictionary. 189 | fname : str 190 | the file to save results to. 191 | compression : str 192 | HDF5 compression method. Defaults to 'gzip'. 193 | """ 194 | # create directory if it didn't exist. 195 | fname = os.path.abspath(fname) 196 | dir_name = os.path.dirname(fname) 197 | if not os.path.exists(dir_name): 198 | os.makedirs(dir_name) 199 | 200 | sweep_info = results['sweep_params'] 201 | with h5py.File(fname, 'w') as f: 202 | for name, swp_vars in sweep_info.items(): 203 | # store data 204 | data = np.asarray(results[name]) 205 | if not data.shape: 206 | dset = f.create_dataset(name, data=data) 207 | else: 208 | dset = f.create_dataset(name, data=data, compression=compression) 209 | # h5py workaround: need to explicitly store unicode 210 | dset.attrs['sweep_params'] = [swp.encode(encoding=bag_encoding, errors=bag_codec_error) 211 | for swp in swp_vars] 212 | 213 | # store sweep parameter values 214 | for var in swp_vars: 215 | if var not in f: 216 | swp_data = results[var] 217 | if np.issubdtype(swp_data.dtype, np.unicode_): 218 | # we need to explicitly encode unicode strings to bytes 219 | swp_data = [v.encode(encoding=bag_encoding, errors=bag_codec_error) for v in swp_data] 220 | 221 | f.create_dataset(var, data=swp_data, compression=compression) 222 | 223 | 224 | def load_sim_file(fname): 225 | """Read simulation results from HDF5 file. 226 | 227 | Parameters 228 | ---------- 229 | fname : str 230 | the file to read. 231 | 232 | Returns 233 | ------- 234 | results : dict[str, any] 235 | the result dictionary. 236 | """ 237 | if not os.path.isfile(fname): 238 | raise ValueError('%s is not a file.' % fname) 239 | 240 | results = {} 241 | sweep_params = {} 242 | with h5py.File(fname, 'r') as f: 243 | for name in f: 244 | dset = f[name] 245 | dset_data = dset[()] 246 | if np.issubdtype(dset.dtype, np.bytes_): 247 | # decode byte values to unicode arrays 248 | dset_data = np.array([v.decode(encoding=bag_encoding, errors=bag_codec_error) for v in dset_data]) 249 | 250 | if 'sweep_params' in dset.attrs: 251 | cur_swp = [swp.decode(encoding=bag_encoding, errors=bag_codec_error) 252 | for swp in dset.attrs['sweep_params']] 253 | results[name] = SweepArray(dset_data, cur_swp) 254 | sweep_params[name] = cur_swp 255 | else: 256 | results[name] = dset_data 257 | 258 | results['sweep_params'] = sweep_params 259 | return results 260 | -------------------------------------------------------------------------------- /bag/interface/server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This class defines SkillOceanServer, a server that handles skill/ocean requests. 4 | 5 | The SkillOceanServer listens for skill/ocean requests from bag. Skill commands will 6 | be forwarded to Virtuoso for execution, and Ocean simulation requests will be handled 7 | by starting an Ocean subprocess. It also provides utility for bag to query simulation 8 | progress and allows parallel simulation. 9 | 10 | Client-side communication: 11 | 12 | the client will always send a request object, which is a python dictionary. 13 | This script processes the request and sends the appropriate commands to 14 | Virtuoso. 15 | 16 | Virtuoso side communication: 17 | 18 | To ensure this process receive all the data from Virtuoso properly, Virtuoso 19 | will print a single line of integer indicating the number of bytes to read. 20 | Then, virtuoso will print out exactly that many bytes of data, followed by 21 | a newline (to flush the standard input). This script handles that protcol 22 | and will strip the newline before sending result back to client. 23 | """ 24 | 25 | import traceback 26 | 27 | import bag.io 28 | 29 | 30 | def _object_to_skill_file_helper(py_obj, file_obj): 31 | """Recursive helper function for object_to_skill_file 32 | 33 | Parameters 34 | ---------- 35 | py_obj : any 36 | the object to convert. 37 | file_obj : file 38 | the file object to write to. Must be created with bag.io 39 | package so that encodings are handled correctly. 40 | """ 41 | # fix potential raw bytes 42 | py_obj = bag.io.fix_string(py_obj) 43 | if isinstance(py_obj, str): 44 | # string 45 | file_obj.write(py_obj) 46 | elif isinstance(py_obj, float): 47 | # prepend type flag 48 | file_obj.write('#float {:f}'.format(py_obj)) 49 | elif isinstance(py_obj, bool): 50 | bool_val = 1 if py_obj else 0 51 | file_obj.write('#bool {:d}'.format(bool_val)) 52 | elif isinstance(py_obj, int): 53 | # prepend type flag 54 | file_obj.write('#int {:d}'.format(py_obj)) 55 | elif isinstance(py_obj, list) or isinstance(py_obj, tuple): 56 | # a list of other objects. 57 | file_obj.write('#list\n') 58 | for val in py_obj: 59 | _object_to_skill_file_helper(val, file_obj) 60 | file_obj.write('\n') 61 | file_obj.write('#end') 62 | elif isinstance(py_obj, dict): 63 | # disembodied property lists 64 | file_obj.write('#prop_list\n') 65 | for key, val in py_obj.items(): 66 | file_obj.write('{}\n'.format(key)) 67 | _object_to_skill_file_helper(val, file_obj) 68 | file_obj.write('\n') 69 | file_obj.write('#end') 70 | else: 71 | raise Exception('Unsupported python data type: %s' % type(py_obj)) 72 | 73 | 74 | def object_to_skill_file(py_obj, file_obj): 75 | """Write the given python object to a file readable by Skill. 76 | 77 | Write a Python object to file that can be parsed into equivalent 78 | skill object by Virtuoso. Currently only strings, lists, and dictionaries 79 | are supported. 80 | 81 | Parameters 82 | ---------- 83 | py_obj : any 84 | the object to convert. 85 | file_obj : file 86 | the file object to write to. Must be created with bag.io 87 | package so that encodings are handled correctly. 88 | """ 89 | _object_to_skill_file_helper(py_obj, file_obj) 90 | file_obj.write('\n') 91 | 92 | 93 | bag_proc_prompt = 'BAG_PROMPT>>> ' 94 | 95 | 96 | class SkillServer(object): 97 | """A server that handles skill commands. 98 | 99 | This server is started and ran by virtuoso. It listens for commands from bag 100 | from a ZMQ socket, then pass the command to virtuoso. It then gather the result 101 | and send it back to bag. 102 | 103 | Parameters 104 | ---------- 105 | router : :class:`bag.interface.ZMQRouter` 106 | the :class:`~bag.interface.ZMQRouter` object used for socket communication. 107 | virt_in : file 108 | the virtuoso input file. Must be created with bag.io 109 | package so that encodings are handled correctly. 110 | virt_out : file 111 | the virtuoso output file. Must be created with bag.io 112 | package so that encodings are handled correctly. 113 | tmpdir : str or None 114 | if given, will save all temporary files to this folder. 115 | """ 116 | 117 | def __init__(self, router, virt_in, virt_out, tmpdir=None): 118 | """Create a new SkillOceanServer instance. 119 | """ 120 | self.handler = router 121 | self.virt_in = virt_in 122 | self.virt_out = virt_out 123 | 124 | # create a directory for all temporary files 125 | self.dtmp = bag.io.make_temp_dir('skillTmp', parent_dir=tmpdir) 126 | 127 | def run(self): 128 | """Starts this server. 129 | """ 130 | while not self.handler.is_closed(): 131 | # check if socket received message 132 | if self.handler.poll_for_read(5): 133 | req = self.handler.recv_obj() 134 | if isinstance(req, dict) and 'type' in req: 135 | if req['type'] == 'exit': 136 | self.close() 137 | elif req['type'] == 'skill': 138 | expr, out_file = self.process_skill_request(req) 139 | if expr is not None: 140 | # send expression to virtuoso 141 | self.send_skill(expr) 142 | msg = self.recv_skill() 143 | self.process_skill_result(msg, out_file) 144 | else: 145 | msg = '*Error* bag server error: bag request:\n%s' % str(req) 146 | self.handler.send_obj(dict(type='error', data=msg)) 147 | else: 148 | msg = '*Error* bag server error: bag request:\n%s' % str(req) 149 | self.handler.send_obj(dict(type='error', data=msg)) 150 | 151 | def send_skill(self, expr): 152 | """Sends expr to virtuoso for evaluation. 153 | 154 | Parameters 155 | ---------- 156 | expr : string 157 | the skill expression. 158 | """ 159 | self.virt_in.write(expr) 160 | self.virt_in.flush() 161 | 162 | def recv_skill(self): 163 | """Receive response from virtuoso""" 164 | num_bytes = int(self.virt_out.readline()) 165 | msg = self.virt_out.read(num_bytes) 166 | if msg[-1] == '\n': 167 | msg = msg[:-1] 168 | return msg 169 | 170 | def close(self): 171 | """Close this server.""" 172 | self.handler.close() 173 | 174 | def process_skill_request(self, request): 175 | """Process the given skill request. 176 | 177 | Based on the given request object, returns the skill expression 178 | to be evaluated by Virtuoso. This method creates temporary 179 | files for long input arguments and long output. 180 | 181 | Parameters 182 | ---------- 183 | request : dict 184 | the request object. 185 | 186 | Returns 187 | ------- 188 | expr : str or None 189 | expression to be evaluated by Virtuoso. If None, an error occurred and 190 | nothing needs to be evaluated 191 | out_file : str or None 192 | if not None, the result will be written to this file. 193 | """ 194 | try: 195 | expr = request['expr'] 196 | input_files = request['input_files'] or {} 197 | out_file = request['out_file'] 198 | except KeyError as e: 199 | msg = '*Error* bag server error: %s' % str(e) 200 | self.handler.send_obj(dict(type='error', data=msg)) 201 | return None, None 202 | 203 | fname_dict = {} 204 | # write input parameters to files 205 | for key, val in input_files.items(): 206 | with bag.io.open_temp(prefix=key, delete=False, dir=self.dtmp) as file_obj: 207 | fname_dict[key] = '"%s"' % file_obj.name 208 | # noinspection PyBroadException 209 | try: 210 | object_to_skill_file(val, file_obj) 211 | except Exception: 212 | stack_trace = traceback.format_exc() 213 | msg = '*Error* bag server error: \n%s' % stack_trace 214 | self.handler.send_obj(dict(type='error', data=msg)) 215 | return None, None 216 | 217 | # generate output file 218 | if out_file: 219 | with bag.io.open_temp(prefix=out_file, delete=False, dir=self.dtmp) as file_obj: 220 | fname_dict[out_file] = '"%s"' % file_obj.name 221 | out_file = file_obj.name 222 | 223 | # fill in parameters to expression 224 | expr = expr.format(**fname_dict) 225 | return expr, out_file 226 | 227 | def process_skill_result(self, msg, out_file=None): 228 | """Process the given skill output, then send result to socket. 229 | 230 | Parameters 231 | ---------- 232 | msg : str 233 | skill expression evaluation output. 234 | out_file : str or None 235 | if not None, read result from this file. 236 | """ 237 | # read file if needed, and only if there are no errors. 238 | if msg.startswith('*Error*'): 239 | # an error occurred, forward error message directly 240 | self.handler.send_obj(dict(type='error', data=msg)) 241 | elif out_file: 242 | # read result from file. 243 | try: 244 | msg = bag.io.read_file(out_file) 245 | data = dict(type='str', data=msg) 246 | except IOError: 247 | stack_trace = traceback.format_exc() 248 | msg = '*Error* error reading file:\n%s' % stack_trace 249 | data = dict(type='error', data=msg) 250 | self.handler.send_obj(data) 251 | else: 252 | # return output from virtuoso directly 253 | self.handler.send_obj(dict(type='str', data=msg)) 254 | -------------------------------------------------------------------------------- /bag/interface/zmqwrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines various wrapper around ZMQ sockets.""" 4 | 5 | import os 6 | import zlib 7 | import pprint 8 | 9 | import yaml 10 | import zmq 11 | 12 | import bag.io 13 | 14 | 15 | class ZMQDealer(object): 16 | """A class that interacts with a ZMQ dealer socket. 17 | 18 | a dealer socket is an asynchronous socket that can issue multiple requests 19 | without needing to wait for an reply. This class encapsulates the ZMQ 20 | socket details and provide more convenient API to use. 21 | 22 | Parameters 23 | ---------- 24 | port : int 25 | the port to connect to. 26 | pipeline : int 27 | number of messages allowed in a pipeline. Only affects file 28 | transfer performance. 29 | host : str 30 | the host to connect to. 31 | log_file : str or None 32 | the log file. None to disable logging. 33 | """ 34 | 35 | def __init__(self, port, pipeline=100, host='localhost', log_file=None): 36 | """Create a new ZMQDealer object. 37 | """ 38 | context = zmq.Context.instance() 39 | # noinspection PyUnresolvedReferences 40 | self.socket = context.socket(zmq.DEALER) 41 | self.socket.hwm = pipeline 42 | self.socket.connect('tcp://%s:%d' % (host, port)) 43 | self._log_file = log_file 44 | self.poller = zmq.Poller() 45 | # noinspection PyUnresolvedReferences 46 | self.poller.register(self.socket, zmq.POLLIN) 47 | 48 | if self._log_file is not None: 49 | self._log_file = os.path.abspath(self._log_file) 50 | # If log file directory does not exists, create it 51 | log_dir = os.path.dirname(self._log_file) 52 | if not os.path.exists(log_dir): 53 | os.makedirs(log_dir) 54 | # clears any existing log 55 | if os.path.exists(self._log_file): 56 | os.remove(self._log_file) 57 | 58 | def log_msg(self, msg): 59 | """Log the given message""" 60 | if self._log_file is not None: 61 | bag.io.write_file(self._log_file, '%s\n' % msg, append=True) 62 | 63 | def log_obj(self, msg, obj): 64 | """Log the given object""" 65 | if self._log_file is not None: 66 | obj_str = pprint.pformat(obj) 67 | bag.io.write_file(self._log_file, '%s\n%s\n' % (msg, obj_str), append=True) 68 | 69 | def close(self): 70 | """Close the underlying socket.""" 71 | self.socket.close() 72 | 73 | def send_obj(self, obj): 74 | """Sends a python object using pickle serialization and zlib compression. 75 | 76 | Parameters 77 | ---------- 78 | obj : any 79 | the object to send. 80 | """ 81 | p = bag.io.to_bytes(yaml.dump(obj)) 82 | z = zlib.compress(p) 83 | self.log_obj('sending data:', obj) 84 | self.socket.send(z) 85 | 86 | def recv_obj(self, timeout=None, enable_cancel=False): 87 | """Receive a python object, serialized with pickle and compressed with zlib. 88 | 89 | Parameters 90 | ---------- 91 | timeout : int or None 92 | the timeout to wait in miliseconds. If None, wait indefinitely. 93 | enable_cancel : bool 94 | If True, allows the user to press Ctrl-C to abort. For this to work, 95 | the other end must know how to process the stop request dictionary. 96 | Returns 97 | ------- 98 | obj : any 99 | the received object. None if timeout reached. 100 | """ 101 | try: 102 | events = self.poller.poll(timeout=timeout) 103 | except KeyboardInterrupt: 104 | if not enable_cancel: 105 | # re-raise exception if cancellation is not enabled. 106 | raise 107 | self.send_obj(dict(type='stop')) 108 | print('Stop signal sent, waiting for reply. Press Ctrl-C again to force exit.') 109 | try: 110 | events = self.poller.poll(timeout=timeout) 111 | except KeyboardInterrupt: 112 | print('Force exiting.') 113 | return None 114 | 115 | if events: 116 | data = self.socket.recv() 117 | z = bag.io.fix_string(zlib.decompress(data)) 118 | obj = yaml.load(z) 119 | self.log_obj('received data:', obj) 120 | return obj 121 | else: 122 | self.log_msg('timeout with %d ms reached.' % timeout) 123 | return None 124 | 125 | def recv_msg(self): 126 | """Receive a string message. 127 | 128 | Returns 129 | ------- 130 | msg : str 131 | the received object. 132 | """ 133 | data = self.socket.recv() 134 | self.log_msg('received message:\n%s' % data) 135 | return data 136 | 137 | 138 | class ZMQRouter(object): 139 | """A class that interacts with a ZMQ router socket. 140 | 141 | a router socket is an asynchronous socket that can receive multiple requests 142 | without needing to issue an reply. This class encapsulates the ZMQ socket 143 | details and provide more convenient API to use. 144 | 145 | Parameters 146 | ---------- 147 | port : int or None 148 | the port to connect to. If None, then a random port between min_port and max_port 149 | will be chosen. 150 | min_port : int 151 | the minimum random port number (inclusive). 152 | max_port : int 153 | the maximum random port number (exclusive). 154 | pipeline : int 155 | number of messages allowed in a pipeline. Only affects file 156 | transfer performance. 157 | log_file : str or None 158 | the log file. None to disable logging. 159 | """ 160 | 161 | def __init__(self, port=None, min_port=5000, max_port=9999, pipeline=100, log_file=None): 162 | """Create a new ZMQDealer object. 163 | """ 164 | context = zmq.Context.instance() 165 | # noinspection PyUnresolvedReferences 166 | self.socket = context.socket(zmq.ROUTER) 167 | self.socket.hwm = pipeline 168 | if port is not None: 169 | self.socket.bind('tcp://*:%d' % port) 170 | self.port = port 171 | else: 172 | self.port = self.socket.bind_to_random_port('tcp://*', min_port=min_port, max_port=max_port) 173 | self.addr = None 174 | self._log_file = log_file 175 | 176 | if self._log_file is not None: 177 | self._log_file = os.path.abspath(self._log_file) 178 | # If log file directory does not exists, create it 179 | log_dir = os.path.dirname(self._log_file) 180 | if not os.path.exists(log_dir): 181 | os.makedirs(log_dir) 182 | # clears any existing log 183 | if os.path.exists(self._log_file): 184 | os.remove(self._log_file) 185 | 186 | def get_port(self): 187 | """Returns the port number.""" 188 | return self.port 189 | 190 | def is_closed(self): 191 | """Returns True if this router is closed.""" 192 | return self.socket.closed 193 | 194 | def close(self): 195 | """Close the underlying socket.""" 196 | self.socket.close() 197 | 198 | def log_msg(self, msg): 199 | """Log the given message""" 200 | if self._log_file is not None: 201 | bag.io.write_file(self._log_file, '%s\n' % msg, append=True) 202 | 203 | def log_obj(self, msg, obj): 204 | """Log the given object""" 205 | if self._log_file is not None: 206 | obj_str = pprint.pformat(obj) 207 | bag.io.write_file(self._log_file, '%s\n%s\n' % (msg, obj_str), append=True) 208 | 209 | def send_msg(self, msg, addr=None): 210 | """Sends a string message 211 | 212 | Parameters 213 | ---------- 214 | msg : str 215 | the message to send. 216 | addr : str or None 217 | the address to send the object to. If None, send to last sender. 218 | """ 219 | addr = addr or self.addr 220 | if addr is None: 221 | warn_msg = '*WARNING* No receiver address specified. Message not sent:\n%s' % msg 222 | self.log_msg(warn_msg) 223 | else: 224 | self.log_msg('sending message:\n%s' % msg) 225 | self.socket.send_multipart([addr, msg]) 226 | 227 | def send_obj(self, obj, addr=None): 228 | """Sends a python object using pickle serialization and zlib compression. 229 | 230 | Parameters 231 | ---------- 232 | obj : any 233 | the object to send. 234 | addr : str or None 235 | the address to send the object to. If None, send to last sender. 236 | """ 237 | addr = addr or self.addr 238 | if addr is None: 239 | warn_msg = '*WARNING* No receiver address specified. Message not sent:' 240 | self.log_obj(warn_msg, obj) 241 | else: 242 | p = bag.io.to_bytes(yaml.dump(obj)) 243 | z = zlib.compress(p) 244 | self.log_obj('sending data:', obj) 245 | self.socket.send_multipart([addr, z]) 246 | 247 | def poll_for_read(self, timeout): 248 | """Poll this socket for given timeout for read event. 249 | 250 | Parameters 251 | ---------- 252 | timeout : int 253 | timeout in miliseconds. 254 | 255 | Returns 256 | ------- 257 | status : int 258 | nonzero value means that this socket is ready for read. 259 | """ 260 | return self.socket.poll(timeout=timeout) 261 | 262 | def recv_obj(self): 263 | """Receive a python object, serialized with pickle and compressed with zlib. 264 | 265 | Returns 266 | ------- 267 | obj : any 268 | the received object. 269 | """ 270 | self.addr, data = self.socket.recv_multipart() 271 | 272 | z = bag.io.fix_string(zlib.decompress(data)) 273 | obj = yaml.load(z) 274 | self.log_obj('received data:', obj) 275 | return obj 276 | 277 | def get_last_sender_addr(self): 278 | """Returns the address of the sender of last received message. 279 | 280 | Returns 281 | ------- 282 | addr : str 283 | the last sender address 284 | """ 285 | return self.addr 286 | -------------------------------------------------------------------------------- /bag/mdao/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module defines core BAG openmdao classes.""" 4 | 5 | import numpy as np 6 | import networkx as nx 7 | import openmdao.api as omdao 8 | 9 | import bag.util.parse 10 | 11 | from .components import VecFunComponent 12 | 13 | 14 | class GroupBuilder(object): 15 | """A class that builds new OpenMDAO groups. 16 | 17 | This class provides a simple interface to define new variables as function of 18 | other variables, and it tracks the variable dependencies using a directed 19 | acyclic graph. 20 | 21 | """ 22 | 23 | def __init__(self): 24 | self._g = nx.DiGraph() 25 | self._input_vars = set() 26 | 27 | def _add_node(self, name, ndim, **kwargs): 28 | """Helper method to add a node and keep track of input variables.""" 29 | self._g.add_node(name, ndim=ndim, **kwargs) 30 | self._input_vars.add(name) 31 | 32 | def _add_edge(self, parent, child): 33 | """Helper method to add an edge and update input variables.""" 34 | self._g.add_edge(parent, child) 35 | try: 36 | self._input_vars.remove(child) 37 | except KeyError: 38 | pass 39 | 40 | def get_inputs(self): 41 | """Returns a set of current input variable names. 42 | 43 | Returns 44 | ------- 45 | input_vars : set[str] 46 | a set of input variable names. 47 | """ 48 | return self._input_vars.copy() 49 | 50 | def get_variables(self): 51 | """Returns a list of variables. 52 | 53 | Returns 54 | ------- 55 | var_list : list[str] 56 | a list of variables. 57 | """ 58 | return list(self._g.nodes_iter()) 59 | 60 | def get_variable_info(self, name): 61 | """Returns the range and dimension of the given variable. 62 | 63 | Parameters 64 | ---------- 65 | name : str 66 | variable name. 67 | 68 | Returns 69 | ------- 70 | min : float 71 | minimum value. 72 | max : float 73 | maximum value. 74 | ndim : int 75 | variable dimension. 76 | """ 77 | nattr = self._g.node[name] 78 | return nattr.copy() 79 | 80 | def add_fun(self, var_name, fun_list, params, param_ranges, vector_params=None): 81 | """Add a new variable defined by the given list of functions. 82 | 83 | Parameters 84 | ---------- 85 | var_name : str 86 | variable name. 87 | fun_list : list[bag.math.interpolate.Interpolator] 88 | list of functions, one for each dimension. 89 | params : list[str] 90 | list of parameter names. Parameter names may repeat, in which case the 91 | same parameter will be used for multiple arguments of the function. 92 | param_ranges : dict[str, (float, float)] 93 | a dictionary of parameter valid range. 94 | vector_params : set[str] 95 | set of parameters that are vector instead of scalar. If a parameter 96 | is a vector, it will be the same size as the output, and each function 97 | only takes in the corresponding element of the parameter. 98 | """ 99 | vector_params = vector_params or set() 100 | ndim = len(fun_list) 101 | 102 | # error checking 103 | for par in params: 104 | if par not in param_ranges: 105 | raise ValueError('Valid range of %s not specified.' % par) 106 | 107 | # add inputs 108 | for par, (par_min, par_max) in param_ranges.items(): 109 | par_dim = ndim if par in vector_params else 1 110 | if par not in self._g: 111 | # add input to graph if it's not in there. 112 | self._add_node(par, par_dim) 113 | 114 | nattrs = self._g.node[par] 115 | if nattrs['ndim'] != par_dim: 116 | # error checking. 117 | raise ValueError('Variable %s has dimension mismatch.' % par) 118 | # update input range 119 | nattrs['min'] = max(par_min, nattrs.get('min', par_min)) 120 | nattrs['max'] = min(par_max, nattrs.get('max', par_max)) 121 | 122 | # add current variable 123 | if var_name not in self._g: 124 | self._add_node(var_name, ndim) 125 | 126 | nattrs = self._g.node[var_name] 127 | # error checking. 128 | if nattrs['ndim'] != ndim: 129 | raise ValueError('Variable %s has dimension mismatch.' % var_name) 130 | if self._g.in_degree(var_name) > 0: 131 | raise Exception('Variable %s already has other dependencies.' % var_name) 132 | 133 | nattrs['fun_list'] = fun_list 134 | nattrs['params'] = params 135 | nattrs['vec_params'] = vector_params 136 | for parent in param_ranges.keys(): 137 | self._add_edge(parent, var_name) 138 | 139 | def add_var(self, variable, vmin, vmax, ndim=1): 140 | """Adds a new independent variable. 141 | 142 | Parameters 143 | ---------- 144 | variable : str 145 | the variable to add 146 | vmin : float 147 | the minimum allowable value. 148 | vmax : float 149 | the maximum allowable value. 150 | ndim : int 151 | the dimension of the variable. Defaults to 1. 152 | """ 153 | if variable in self._g: 154 | raise Exception('Variable %s already exists.' % variable) 155 | self._add_node(variable, ndim, min=vmin, max=vmax) 156 | 157 | def set_input_limit(self, var, equals=None, lower=None, upper=None): 158 | """Sets the limit on the given input variable. 159 | 160 | Parameters 161 | ---------- 162 | var : str 163 | name of the variable. 164 | equals : float or None 165 | if given, the equality value. 166 | lower : float or None 167 | if given, the minimum. 168 | upper : float or None 169 | if given, the maximum. 170 | """ 171 | if var in self._g: 172 | if self._g.in_degree(var) > 0: 173 | raise Exception('Variable %s is not an input variable' % var) 174 | nattr = self._g.node[var] 175 | if equals is not None: 176 | nattr['equals'] = equals 177 | lower = upper = equals 178 | print(var, lower, upper) 179 | if lower is not None: 180 | nattr['min'] = max(nattr.get('min', lower), lower) 181 | if upper is not None: 182 | nattr['max'] = min(nattr.get('max', upper), upper) 183 | print(var, nattr['min'], nattr['max']) 184 | 185 | def add_expr(self, eqn, ndim): 186 | """Adds a new variable with the given expression. 187 | 188 | Parameters 189 | ---------- 190 | eqn : str 191 | An equation of the form " = ", where var 192 | is the output variable name, and expr is the expression. 193 | All variables in expr must be already added. 194 | ndim : int 195 | the dimension of the output variable. 196 | """ 197 | variable, expr = eqn.split('=', 1) 198 | variable = variable.strip() 199 | expr = expr.strip() 200 | 201 | if variable not in self._g: 202 | self._add_node(variable, ndim) 203 | nattrs = self._g.node[variable] 204 | if nattrs['ndim'] != ndim: 205 | raise Exception('Dimension mismatch for %s' % variable) 206 | if self._g.in_degree(variable) > 0: 207 | raise Exception('%s already depends on other variables' % variable) 208 | 209 | invars = bag.util.parse.get_variables(expr) 210 | for parent in invars: 211 | if parent not in self._g: 212 | raise Exception('Variable %s is not defined.' % parent) 213 | self._add_edge(parent, variable) 214 | 215 | nattrs['expr'] = expr 216 | 217 | def build(self, debug=False): 218 | """Returns a OpenMDAO Group from the variable graph. 219 | 220 | Parameters 221 | ---------- 222 | debug : bool 223 | True to print debug messages. 224 | 225 | Returns 226 | ------- 227 | grp : omdao.Group 228 | the OpenMDAO group that computes all variables. 229 | input_bounds : dict[str, any] 230 | a dictionary from input variable name to (min, max, ndim) tuple. 231 | """ 232 | input_bounds = {} 233 | ndim_dict = {} 234 | 235 | if not nx.is_directed_acyclic_graph(self._g): 236 | raise Exception('Dependency loop detected') 237 | 238 | grp = omdao.Group() 239 | prom = ['*'] 240 | for var in nx.topological_sort(self._g): 241 | nattrs = self._g.node[var] 242 | ndim = nattrs['ndim'] 243 | ndim_dict[var] = ndim 244 | if self._g.in_degree(var) == 0: 245 | if debug: 246 | # input variable 247 | print('Input variable: %s' % var) 248 | # range checking 249 | vmin, vmax = nattrs['min'], nattrs['max'] 250 | veq = nattrs.get('equals', None) 251 | if vmin > vmax: 252 | raise Exception('Variable %s input range not valid.' % var) 253 | input_bounds[var] = veq, vmin, vmax, ndim 254 | else: 255 | init_vals = {par: np.zeros(ndim_dict[par]) for par in self._g.predecessors_iter(var)} 256 | comp_name = 'comp__%s' % var 257 | if 'expr' in nattrs: 258 | eqn = '{}={}'.format(var, nattrs['expr']) 259 | init_vals[var] = np.zeros(ndim) 260 | # noinspection PyTypeChecker 261 | grp.add(comp_name, omdao.ExecComp(eqn, **init_vals), promotes=prom) 262 | elif 'fun_list' in nattrs: 263 | params = nattrs['params'] 264 | fun_list = nattrs['fun_list'] 265 | vec_params = nattrs['vec_params'] 266 | comp = VecFunComponent(var, fun_list, params, vector_params=vec_params) 267 | # noinspection PyTypeChecker 268 | grp.add(comp_name, comp, promotes=prom) 269 | else: 270 | raise Exception('Unknown attributes: {}'.format(nattrs)) 271 | 272 | return grp, input_bounds 273 | --------------------------------------------------------------------------------