blob: 3f13bb18a9893cb077c07baa016c84e59ecac4ec [file] [log] [blame]
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Module 2: XBase Routing API\n",
"In this module, you will learn the basics about the routing grid system in XBase. We will go over how tracks are defined, how to create wires, vias and pins, and how to define the size of a layout cell."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## XBase Routing Grid\n",
"\n",
"```yaml\n",
"routing_grid:\n",
" layers: [4, 5, 6, 7]\n",
" spaces: [0.06, 0.1, 0.12, 0.2]\n",
" widths: [0.06, 0.1, 0.12, 0.2]\n",
" bot_dir: 'x'\n",
"```\n",
"In XBase, all wires and vias have to be drawn on the routing grid, which is usually defined in a specification file, as shown above. On each layer, all wires must travel in the same direction (horizontal or vertical), and wire direction alternates between each layers. The routing grid usually starts on an intermediate layer (metal 4 in the above example), and lower layers are reserved for device primitives routing. As seen above, different layers can define different wire pitch, with the wire pitch generally increasing as you move up the metal stack.\n",
"\n",
"All layout cell dimensions in XBase must also be quantized to the routing grid, meaning that a layout cell must contain integer number of tracks on all metal layers it uses. Because of the difference in wire pitch, a layout cell that use more layers generally have coarser quantization compared with a layout cell that use fewer layers."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## XBase Routing Tracks\n",
"\n",
"<img src=\"bootcamp_pics/2_xbase_routing/xbase_routing_1.PNG\" alt=\"Drawing\" style=\"width: 300px\"/>\n",
"The figure above shows some wires drawn in XBase. Track pitch is the sum of unit width and space, and track number 0 is defined as the wire that's half-pitch away from left or bottom boundary. From the figure, you can see spacing between wires follows the formula $S = sp + N \\cdot p$, where $N$ is the number of tracks in between.\n",
"\n",
"<img src=\"bootcamp_pics/2_xbase_routing/xbase_routing_2.PNG\" alt=\"Drawing\" style=\"width: 400px\"/>\n",
"XBase also supports drawing thicker wires by using multiple adjacent tracks. Wire width follows the formula $W = w + (N - 1)\\cdot p$, where $N$ is the number of tracks a wire uses. One issue with this scheme is that even width wires wastes more space compared to odd width wires. For example, in the above figure, although tracks 1 and 3 are empty, no wire can be drawn there because it will violate minimum spacing rule to the wire centered on track 2. As a result, the wire on track 2 takes up 3 tracks although it is only 2 tracks wide.\n",
"\n",
"<img src=\"bootcamp_pics/2_xbase_routing/xbase_routing_3.PNG\" alt=\"Drawing\" style=\"width: 400px\"/>\n",
"To work around this issues, XBase allows you to place wires on half-integer tracks. In the above figure, the 2 tracks wide wire is moved to track 1.5 from track 2, and thus wires can still be drawn on tracks 0 and 3, making the layout more space efficient. As an added benefit, track -0.5 is now on top of the left-most/bottom-most boundary, so it is now possible to share a wire with adjacent layout cells, such as supply wires in a custom digital standard cell."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## TrackID and WireArray\n",
"```python\n",
"class TrackID(object):\n",
" def __init__(self, layer_id, track_idx, width=1, num=1, pitch=0.0):\n",
" #type: (int, Union[float, int], int, int, Union[float, int]) -> None\n",
" \n",
"class WireArray(object):\n",
" def __init__(self, track_id, lower, upper):\n",
" #type: (TrackID, float, float) -> None\n",
"```\n",
"\n",
"Routing track locations are represented by the `TrackID` Python object. It has built-in support for drawing a multi-wire bus by specifying the optional `num` and `pitch` parameters, which defines the number of wires in the bus and the number of track pitches between adjacent wires. The `layer_id` parameter specifies the routing layer ID, the `track_idx` parameter specifies the track index of the left-most/bottom-most wire, and `width` specifies the number of tracks a single wire uses.\n",
"\n",
"Physical wires in XBase are represented by the `WireArray` Python object. It contains a `TrackID` object describes the location of the wires, and `lower` and `upper` attributes describes the starting and ending coordinate of those wires along the track. For example, a horizontal wire starting at $x = 0.5$ um and ending at $x = 3.0$ um will have `lower = 0.5`, and `upper = 3.0`.\n",
"\n",
"One last note is that layout pins can only be added on `WireArray` objects. This guarantees that pins of a layout cell will always be on the routing grid."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BAG Layout Generation Code\n",
"```python\n",
"def gen_layout(prj, specs, dsn_name, demo_class):\n",
" # get information from specs\n",
" dsn_specs = specs[dsn_name]\n",
" impl_lib = dsn_specs['impl_lib']\n",
" layout_params = dsn_specs['layout_params']\n",
" gen_cell = dsn_specs['gen_cell']\n",
"\n",
" # create layout template database\n",
" tdb = make_tdb(prj, specs, impl_lib)\n",
" # compute layout\n",
" print('computing layout')\n",
" # template = tdb.new_template(params=layout_params, temp_cls=temp_cls)\n",
" template = tdb.new_template(params=layout_params, temp_cls=demo_class)\n",
"\n",
" # create layout in OA database\n",
" print('creating layout')\n",
" tdb.batch_layout(prj, [template], [gen_cell])\n",
" # return corresponding schematic parameters\n",
" print('layout done')\n",
" return template.sch_params\n",
"```\n",
"The above code snippet (taking from `xbase_demo.core` module) shows how layout is generated. First, user create a layout database object, which keeps track of layout hierarchy. Then, user uses the layout database object to create new layout instances given layout generator class and parameters. Finally, layout database uses `BagProject` instance to create the generated layouts in Virtuoso. The generated layout will also contain the corresponding schematic parameters, which can be passed to schematic generator later."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BAG TemplateDB Creation Code\n",
"```python\n",
"def make_tdb(prj, specs, impl_lib):\n",
" grid_specs = specs['routing_grid']\n",
" layers = grid_specs['layers']\n",
" spaces = grid_specs['spaces']\n",
" widths = grid_specs['widths']\n",
" bot_dir = grid_specs['bot_dir']\n",
"\n",
" # create RoutingGrid object\n",
" routing_grid = RoutingGrid(prj.tech_info, layers, spaces, widths, bot_dir)\n",
" # create layout template database\n",
" tdb = TemplateDB('template_libs.def', routing_grid, impl_lib, use_cybagoa=True)\n",
" return tdb\n",
"```\n",
"For reference, the above code snippet shows how the layout database object is created. A `RoutingGrid` object is created from routing grid parameters specified in the specification file, which is then used to construct the `TemplateDB` layout database object."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Routing Example\n",
"\n",
"The code box below defines a `RoutingDemo` layout generator class, which is a simply layout containing only wires, vias, and pins. All layout creation happens in the `draw_layout()` function, the functions/attributes of interests are:\n",
"\n",
"* `add_wires()`: Create one or more physical wires, with the given options.\n",
"* `connect_to_tracks()`: Connect two `WireArray`s on adjacent layers by extending them to their intersection and adding vias.\n",
"* `connnect_wires()`: Connect multiple `WireArrays` on the same layer together. \n",
"* `add_pin()`: Add a pin object on top of a `WireArray` object.\n",
"* `self.size`: A 3-tuple describing the size of this layout cell.\n",
"* `self.bound_box`: A `BBox` object representing the bounding box of this layout cell, computed from `self.size`.\n",
"\n",
"To see the layout in action, evaluate the code box below by selecting the cell and pressing Ctrl+Enter. A `DEMO_ROUTING` library will be created in Virtuoso with a single `ROUTING_DEMO` layout cell in it. Feel free to play around with the numbers and re-evaluating the cell, and the layout in Virtuoso should update.\n",
"\n",
"Exercise 1: There are currently 3 wires labeled \"pin3\". Change that to 4 wires by adding an extra wire with the same pitch on the right.\n",
"\n",
"Exercise 2: Connect all wires labeled \"pin3\" to the wire labeled \"pin1\". Hint: use `connect_to_tracks()` and `connect_wires()`."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"using existing BagProject\n",
"computing layout\n",
"WireArray(TrackID(layer=4, track=0), 0.1, 0.3)\n",
"0.1 0.2 0.3\n",
"TrackID(layer=4, track=0)\n",
"WireArray(TrackID(layer=5, track=6, num=3, pitch=2), 0.1, 0.4)\n",
"WireArray(TrackID(layer=5, track=3, width=2), 0.042, 0.75)\n",
"BBox(0.000, 0.000, 1.980, 0.864)\n",
"creating layout\n",
"layout done\n"
]
}
],
"source": [
"from bag.layout.routing import TrackID\n",
"from bag.layout.template import TemplateBase\n",
"\n",
"\n",
"class RoutingDemo(TemplateBase):\n",
" def __init__(self, temp_db, lib_name, params, used_names, **kwargs):\n",
" super(RoutingDemo, self).__init__(temp_db, lib_name, params, used_names, **kwargs)\n",
"\n",
" @classmethod\n",
" def get_params_info(cls):\n",
" return {}\n",
"\n",
" def draw_layout(self):\n",
" # Metal 4 is horizontal, Metal 5 is vertical\n",
" hm_layer = 4\n",
" vm_layer = 5\n",
"\n",
" # add a horizontal wire on track 0, from X=0.1 to X=0.3\n",
" warr1 = self.add_wires(hm_layer, 0, 0.1, 0.3)\n",
" # print WireArray object\n",
" print(warr1)\n",
" # print lower, middle, and upper coordinate of wire.\n",
" print(warr1.lower, warr1.middle, warr1.upper)\n",
" # print TrackID object associated with WireArray\n",
" print(warr1.track_id)\n",
"\n",
" # add a horizontal wire on track 1, from X=0.1 to X=0.3,\n",
" # coordinates specified in resolution units\n",
" warr2 = self.add_wires(hm_layer, 1, 100, 300, unit_mode=True)\n",
"\n",
" # add another wire on track 1, from X=0.35 to X=0.45\n",
" warr2_ext = self.add_wires(hm_layer, 1, 350, 450, unit_mode=True)\n",
" # connect wires on the same track, in this case warr2 and warr2_ext\n",
" self.connect_wires([warr2, warr2_ext])\n",
" \n",
" # add a horizontal wire on track 2.5, from X=0.2 to X=0.4\n",
" self.add_wires(hm_layer, 2.5, 200, 400, unit_mode=True)\n",
" # add a horizontal wire on track 4, from X=0.2 to X=0.4, with 2 tracks wide\n",
" warr3 = self.add_wires(hm_layer, 4, 200, 400, width=2, unit_mode=True)\n",
"\n",
" # add 3 parallel vertical wires starting on track 6 and use every other track\n",
" warr4 = self.add_wires(vm_layer, 6, 100, 400, num=3, pitch=2, unit_mode=True)\n",
" print(warr4)\n",
" \n",
" # create a TrackID object representing a vertical track\n",
" tid = TrackID(vm_layer, 3, width=2, num=1, pitch=0)\n",
" # connect horizontal wires to the vertical track\n",
" warr5 = self.connect_to_tracks([warr1, warr3], tid)\n",
" print(warr5)\n",
"\n",
" # add a pin on a WireArray\n",
" self.add_pin('pin1', warr1)\n",
" # add a pin, but make label different than net name. Useful for LVS connect\n",
" self.add_pin('pin2', warr2, label='pin2:')\n",
" # add_pin also works for WireArray representing multiple wires\n",
" self.add_pin('pin3', warr4)\n",
" # add a pin (so it is visible in BAG), but do not create the actual layout\n",
" # in OA. This is useful for hiding pins on lower levels of hierarchy.\n",
" self.add_pin('pin4', warr3, show=False)\n",
"\n",
" # set the size of this template\n",
" top_layer = vm_layer\n",
" num_h_tracks = 6\n",
" num_v_tracks = 11\n",
" # size is 3-element tuple of top layer ID, number of top\n",
" # vertical tracks, and number of top horizontal tracks\n",
" self.size = top_layer, num_v_tracks, num_h_tracks\n",
" # print bounding box of this template\n",
" print(self.bound_box)\n",
" # add a M7 rectangle to visualize bounding box in layout\n",
" self.add_rect('M7', self.bound_box)\n",
" \n",
"\n",
"import os\n",
"\n",
"# import bag package\n",
"import bag\n",
"from bag.io import read_yaml\n",
"\n",
"# import BAG demo Python modules\n",
"import xbase_demo.core as demo_core\n",
"\n",
"# load circuit specifications from file\n",
"spec_fname = os.path.join(os.environ['BAG_WORK_DIR'], 'specs_demo/demo.yaml')\n",
"top_specs = read_yaml(spec_fname)\n",
"\n",
"# obtain BagProject instance\n",
"local_dict = locals()\n",
"if 'bprj' in local_dict:\n",
" print('using existing BagProject')\n",
" bprj = local_dict['bprj']\n",
"else:\n",
" print('creating BagProject')\n",
" bprj = bag.BagProject()\n",
"\n",
"demo_core.routing_demo(bprj, top_specs, RoutingDemo)"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.3"
}
},
"nbformat": 4,
"nbformat_minor": 1
}