├── 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 |
--------------------------------------------------------------------------------