blob: d6c02156a02b7177baf1153c576cbb70900243dd [file] [log] [blame]
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# AnalogBase\n",
"In this module, you will learn the basics of `AnalogBase`, and how to design a source-follower layout generator using AnalogBase.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## What is AnalogBase?\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_1.PNG\" alt=\"Drawing\" style=\"width: 200px;\" />\n",
"`AnalogBase` is one of several \"layout floorplan\" classes that allows designers to easily develop process-portable layout generator for various electromigration-constrained circuits. To do so, `AnalogBase` draws rows of transistors with substrate contacts on the top-most and bottom-most rows, as shown in the figure above. In this floorplan, the number of current-carrying wires scales naturally with number of fingers, which is optimal for circuits with large bias currents.\n",
"\n",
"By convention, `AnalogBase` draws $N$ rows of NMOS (labeled as `nch`) and $P$ rows of PMOS (labeled as `pch`), with $N$ and $P$ being nonnegative integers, so you can only draw NMOS rows by setting $P=0$, and so on. The rows are indexed from bottom to top, so `nch(N-1)` is the top-most NMOS row, and `pch0` is the bottom-most PMOS row.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Transistor Source/Drain Naming Convention\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_3.PNG\" alt=\"Drawing\" style=\"width: 600px;\"/>\n",
"Before we talk about how `AnalogBase` draws transistor connections, we need to establish a naming convention for source/drain junctions of a transistor, since source and drain are often interchangeable. In XBase, the left-most source/drain junction of a transistor is always called \"source\", and after that source and drain alternates between each other, as shown in the above figure. This implies that for even number of fingers, the right-most junction is always \"source\", and for odd number of fingers, the left-most junction is always \"drain\"."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## AnalogMosConn Overview\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_2.PNG\" alt=\"Drawing\" style=\"width: 600px;\"/>\n",
"To connect transistors to the routing grid, `AnalogBase` \"drops\" `AnalogMosConn`, a layout cell consisting only of wires and vias, on top of desired transistors to connect gates, sources, and drains to a vertical routing layer. For most technologies, `AnalogMosConn` draws gate, drain, and source wires on every other source/drain junction, with drain and source wires interleaving with each other. By default, the gate wires are drawn below the transistor row, to draw gate wires above the transistor row, flip the row upside down by changing the row orientation from `R0` to `MX` (we will see an example of this later).\n",
"\n",
"With this layout style, the gate wires can either be drawn in the same tracks as source wires (\"G aligned to S\"), or they can be drawn in the same tracks as drain wires (\"G aligned to D\"). The gate wire location is usually determined by source/drain wire direction. For example, in the figure above, if the source of a transistor needs to be connected to the row below it, then gate wires cannot be aligned to source, as this will cause a short between gate and source wires when the source wires is extended downwards. Because of this, when creating a `AnalogMosConn`, designer needs to specify the drain and source wire directions (whether they go \"up\" or \"down\"), and the gate wire locations will be determined automatically to avoid shorts."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connecting to Horizontal Tracks\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_4.PNG\" alt=\"Drawing\" style=\"width: 300px;\"/>\n",
"In the previous section, we see that `AnalogMosConn` connects the transistor to vertical tracks. How do we connect those vertical wires to the horizontal tracks above it? If you recall from the previous module, you would need to use the `connect_to_tracks()` method with the horizontal track index. The question now becomes: how do I know which track index can be used for gate/drain/source connections?\n",
"\n",
"To get a better understanding of this problem, consider the layout shown in the figure above. The PMOS drain wires can be easily connected to track 10 with no issues, but the PMOS gate wires cannot be connected to track 10 without shorting with drain wires. In fact, the PMOS gate wires can only be connected to tracks 5, 6, and 7 without running into minimum line-end spacing rules with other wires. How can we determine what the legal track indices are? Furthermore, if our particular circuit requires more than 3 horizontal tracks for PMOS gate connections, how can we tell `AnalogBase` to space the rows further apart?\n",
"\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_5.PNG\" alt=\"Drawing\" style=\"width: 300px;\"/>\n",
"To address these issues, `AnalogBase` introduces the concept of relative track indices, as shown in the figure above. `AnalogBase` categorizes each horizontal tracks by the transistor row it belongs to, and by whether it can be connected to the gate/drain/source wires without DRC errors. \n",
"\n",
"In each row, `g0` is the horizontal track furthest from the transistor row that can be connected to the gate wires without errors, and the index increases as the wire moves closer to the transistor. `ds0` is the horizontal track closest to the transistor row (perhaps on top of it) that can be connected to the drain/source wires without errors, and the index increases as the wire moves away from the transistor.\n",
"\n",
"`AnalogBase` provides two methods to convert relative track indices to absolute track numbers, which can then be passed to `connect_to_tracks()` method to draw the connections. Using the figure above as an example, `self.get_track_index('pch', 0, 'g', 1)` will returns the track number of the horizontal track at PMOS row 0, gate type, index 1, which is track number 5. `self.make_track_id('pch', 0, 'g', 1)` will return the corresponding `TrackID` object instead.\n",
"\n",
"Finally, designer can specify the number of horizontal tracks needed for gate/drain/source connections on each row, and `AnalogBase` will automatically move rows further apart if necessary."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## CS Amplifier Layout Example\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_6.PNG\" alt=\"Drawing\" style=\"width: 400px;\"/>\n",
"Now that you have a general idea of how `AnalogBase` works, lets walk through a common-source amplifier example. The figure above shows a rough sketch of the layout floorplan (**NOTE: ALWAYS DRAW FLOORPLAN BEFORE CODING!**). We have one NMOS row on the bottom, one PMOS row on the top, and we put extra dummy transistors on both sides to reduce edge layout effects. The input connects to NMOS gates from below the NMOS row, the PMOS bias connects to PMOS gates from above the PMOS row, and the output drain/source of NMOS/PMOS are connected to a horizontal track between the two rows. Finally, the supply drain/source wires are extended and shorted on top of the substrate contacts on both ends.\n",
"\n",
"The entire common-source amplifier layout generator code is reproduced below. We will walk through important sections of the code and describe what they do.\n",
"\n",
"```python\n",
"class AmpCS(AnalogBase):\n",
" \"\"\"A common source amplifier.\"\"\"\n",
" def __init__(self, temp_db, lib_name, params, used_names, **kwargs):\n",
" AnalogBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs)\n",
" self._sch_params = None\n",
"\n",
" @property\n",
" def sch_params(self):\n",
" return self._sch_params\n",
"\n",
" @classmethod\n",
" def get_params_info(cls):\n",
" \"\"\"Returns a dictionary containing parameter descriptions.\n",
"\n",
" Override this method to return a dictionary from parameter names to descriptions.\n",
"\n",
" Returns\n",
" -------\n",
" param_info : dict[str, str]\n",
" dictionary from parameter name to description.\n",
" \"\"\"\n",
" return dict(\n",
" lch='channel length, in meters.',\n",
" w_dict='width dictionary.',\n",
" intent_dict='intent dictionary.',\n",
" fg_dict='number of fingers dictionary.',\n",
" ndum='number of dummies on each side.',\n",
" ptap_w='NMOS substrate width, in meters/number of fins.',\n",
" ntap_w='PMOS substrate width, in meters/number of fins.',\n",
" show_pins='True to draw pin geometries.',\n",
" )\n",
"\n",
" def draw_layout(self):\n",
" \"\"\"Draw the layout of a transistor for characterization.\n",
" \"\"\"\n",
"\n",
" lch = self.params['lch']\n",
" w_dict = self.params['w_dict']\n",
" intent_dict = self.params['intent_dict']\n",
" fg_dict = self.params['fg_dict']\n",
" ndum = self.params['ndum']\n",
" ptap_w = self.params['ptap_w']\n",
" ntap_w = self.params['ntap_w']\n",
" show_pins = self.params['show_pins']\n",
"\n",
" fg_amp = fg_dict['amp']\n",
" fg_load = fg_dict['load']\n",
"\n",
" if fg_load % 2 != 0 or fg_amp % 2 != 0:\n",
" raise ValueError('fg_load=%d and fg_amp=%d must all be even.' % (fg_load, fg_amp))\n",
"\n",
" # compute total number of fingers in each row\n",
" fg_half_pmos = fg_load // 2\n",
" fg_half_nmos = fg_amp // 2\n",
" fg_half = max(fg_half_pmos, fg_half_nmos)\n",
" fg_tot = (fg_half + ndum) * 2\n",
"\n",
" # specify width/threshold of each row\n",
" nw_list = [w_dict['amp']]\n",
" pw_list = [w_dict['load']]\n",
" nth_list = [intent_dict['amp']]\n",
" pth_list = [intent_dict['load']]\n",
"\n",
" # specify number of horizontal tracks for each row\n",
" ng_tracks = [1] # input track\n",
" nds_tracks = [1] # one track for space\n",
" pds_tracks = [1] # output track\n",
" pg_tracks = [1] # bias track\n",
"\n",
" # specify row orientations\n",
" n_orient = ['R0'] # gate connection on bottom\n",
" p_orient = ['MX'] # gate connection on top\n",
"\n",
" self.draw_base(lch, fg_tot, ptap_w, ntap_w, nw_list,\n",
" nth_list, pw_list, pth_list,\n",
" ng_tracks=ng_tracks, nds_tracks=nds_tracks,\n",
" pg_tracks=pg_tracks, pds_tracks=pds_tracks,\n",
" n_orientations=n_orient, p_orientations=p_orient,\n",
" )\n",
"\n",
" # figure out if output connects to drain or source of nmos\n",
" if (fg_amp - fg_load) % 4 == 0:\n",
" s_net, d_net = '', 'vout'\n",
" aout, aoutb, nsdir, nddir = 'd', 's', 0, 2\n",
" else:\n",
" s_net, d_net = 'vout', ''\n",
" aout, aoutb, nsdir, nddir = 's', 'd', 2, 0\n",
"\n",
" # create transistor connections\n",
" load_col = ndum + fg_half - fg_half_pmos\n",
" amp_col = ndum + fg_half - fg_half_nmos\n",
" amp_ports = self.draw_mos_conn('nch', 0, amp_col, fg_amp, nsdir, nddir,\n",
" s_net=s_net, d_net=d_net)\n",
" load_ports = self.draw_mos_conn('pch', 0, load_col, fg_load, 2, 0,\n",
" s_net='', d_net='vout')\n",
" # amp_ports/load_ports are dictionaries of WireArrays representing\n",
" # transistor ports.\n",
" print(amp_ports)\n",
" print(amp_ports['g'])\n",
"\n",
" # create TrackID from relative track index\n",
" vin_tid = self.make_track_id('nch', 0, 'g', 0)\n",
" vbias_tid = self.make_track_id('pch', 0, 'g', 0)\n",
" # can also convert from relative to absolute track index\n",
" print(self.get_track_index('nch', 0, 'g', 0))\n",
" # get output track index, put it in the middle\n",
" vout_bot = self.get_track_index('nch', 0, 'ds', 0)\n",
" vout_top = self.get_track_index('pch', 0, 'ds', 0)\n",
" vout_index = self.grid.get_middle_track(vout_bot, vout_top, round_up=True)\n",
" vout_tid = TrackID(self.mos_conn_layer + 1, vout_index)\n",
"\n",
" vin_warr = self.connect_to_tracks(amp_ports['g'], vin_tid)\n",
" vout_warr = self.connect_to_tracks([amp_ports[aout], load_ports['d']], vout_tid)\n",
" vbias_warr = self.connect_to_tracks(load_ports['g'], vbias_tid)\n",
" self.connect_to_substrate('ptap', amp_ports[aoutb])\n",
" self.connect_to_substrate('ntap', load_ports['s'])\n",
"\n",
" vss_warrs, vdd_warrs = self.fill_dummy()\n",
"\n",
" self.add_pin('VSS', vss_warrs, show=show_pins)\n",
" self.add_pin('VDD', vdd_warrs, show=show_pins)\n",
" self.add_pin('vin', vin_warr, show=show_pins)\n",
" self.add_pin('vout', vout_warr, show=show_pins)\n",
" self.add_pin('vbias', vbias_warr, show=show_pins)\n",
"\n",
" # compute schematic parameters\n",
" self._sch_params = dict(\n",
" lch=lch,\n",
" w_dict=w_dict,\n",
" intent_dict=intent_dict,\n",
" fg_dict=fg_dict,\n",
" dum_info=self.get_sch_dummy_info(),\n",
" )\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Class Definition\n",
"```python\n",
"class AmpCS(AnalogBase):\n",
" \"\"\"A common source amplifier.\"\"\"\n",
" def __init__(self, temp_db, lib_name, params, used_names, **kwargs):\n",
" AnalogBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs)\n",
" self._sch_params = None\n",
" \n",
" @property\n",
" def sch_params(self):\n",
" return self._sch_params\n",
"```\n",
"The layout generator code starts with the Python class definition. We subclass the `AnalogBase` class to inherit various functions described earlier. The constructor doesn't do much besides calling the super constructor and initializing a private attribute. Finally, we declare a read-only property, `sch_params`, which we will compute later. It contains the schematic parameters for the schematic generator we will see in the next module."
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Parameter Specifications\n",
"```python\n",
"@classmethod\n",
"def get_params_info(cls):\n",
" \"\"\"Returns a dictionary containing parameter descriptions.\n",
" Override this method to return a dictionary from parameter names to descriptions.\n",
" Returns\n",
" -------\n",
" param_info : dict[str, str]\n",
" dictionary from parameter name to description.\n",
" \"\"\"\n",
" return dict(\n",
" lch='channel length, in meters.',\n",
" w_dict='width dictionary.',\n",
" intent_dict='intent dictionary.',\n",
" fg_dict='number of fingers dictionary.',\n",
" ndum='number of dummies on each side.',\n",
" ptap_w='NMOS substrate width, in meters/number of fins.',\n",
" ntap_w='PMOS substrate width, in meters/number of fins.',\n",
" show_pins='True to draw pin geometries.',\n",
" )\n",
"```\n",
"Next we have a class method, `get_params_info()`, that simply returns a Python dictionary from layout parameter names to a brief description of the corresponding parameter. This method should list all layout parameters, and it is used to determine to compute a unique ID to represent the generated instance. This allows XBase to avoid re-generating existing layouts when constructing a complex layout hierarchy with many duplicate layout instances."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## How many fingers in a row?\n",
"Next, in the `draw_layout()` method is where all the layout generation happens. The beginning is rather straight-forward, then we get to the following section:\n",
"```python\n",
" # compute total number of fingers in each row\n",
"fg_half_pmos = fg_load // 2\n",
"fg_half_nmos = fg_amp // 2\n",
"fg_half = max(fg_half_pmos, fg_half_nmos)\n",
"fg_tot = (fg_half + ndum) * 2\n",
"```\n",
"This section computes how many fingers we need to draw in each transistor row. To get a better understanding, consider the two scenarios below:\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_7.PNG\" alt=\"Drawing\" style=\"width: 600px;\" />\n",
"Since `AnalogBase` must draw the same number of fingers for each row, we see that total number of fingers in each row depends on whether the AMP transistor or the LOAD transistor has more fingers. We resolve this by using the `max()` function to get the larger of the two."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Drawing Transistor Rows\n",
"```python\n",
"# specify width/threshold of each row\n",
"nw_list = [w_dict['amp']]\n",
"pw_list = [w_dict['load']]\n",
"nth_list = [intent_dict['amp']]\n",
"pth_list = [intent_dict['load']]\n",
"\n",
"# specify number of horizontal tracks for each row\n",
"ng_tracks = [1] # input track\n",
"nds_tracks = [1] # one track for space\n",
"pds_tracks = [1] # output track\n",
"pg_tracks = [1] # bias track\n",
"\n",
"# specify row orientations\n",
"n_orient = ['R0'] # gate connection on bottom\n",
"p_orient = ['MX'] # gate connection on top\n",
"\n",
"self.draw_base(lch, fg_tot, ptap_w, ntap_w, nw_list,\n",
" nth_list, pw_list, pth_list,\n",
" ng_tracks=ng_tracks, nds_tracks=nds_tracks,\n",
" pg_tracks=pg_tracks, pds_tracks=pds_tracks,\n",
" n_orientations=n_orient, p_orientations=p_orient,\n",
" )\n",
"```\n",
"This section specifies the layout parameters for each row, then calls the `draw_base()` method in `AnalogBase` to draw the transistor and substrate contact rows. Note that the PMOS row orientation is set to `MX` so that `AnalogMosConn` will draw gate wires on the top of PMOS row."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Is output on source or drain?\n",
"```python\n",
"# figure out if output connects to drain or source of nmos\n",
"if (fg_amp - fg_load) % 4 == 0:\n",
" aout, aoutb, nsdir, nddir = 'd', 's', 0, 2\n",
"else:\n",
" aout, aoutb, nsdir, nddir = 's', 'd', 2, 0\n",
"```\n",
"This section determines if the output should connect to drain or source of the nmos transistor, and as the result what should the nmos source/drain wire directions be. To see why this is necessary, consider the two cases shown below:\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_8.PNG\" alt=\"Drawing\" style=\"width: 600px;\" />\n",
"In both cases, we have 8 PMOS fingers, and 4 or 6 NMOS fingers, respectively. To make life simpler, we decide to always connect the output wires to PMOS drain (if you expect PMOS to always be larger, this gives you less parasitic capacitance). Furthermore, to have better symmetric, we align the center of the PMOS and NMOS transistors. Then, to minimize interconnect resistance, we should connect output to the NMOS junction that is aligned to PMOS drain. If we check the above figure, we see that the corresponding NMOS junction is drain when NMOS has 4 fingers, but it is source when NMOS has 6 fingers! This means that the correct NMOS junction to connect to actually depends on both `fg_amp` and `fg_load`. By sketching a few example, you should be able to figure out that we need to connect output to NMOS drain if the difference in number of fingers is a multiple of 4, and connect output to NMOS drain otherwise. This is exactly what this section of code does."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Drawing Transistor Connections\n",
"```python\n",
"# create transistor connections\n",
"load_col = ndum + fg_half - fg_half_pmos\n",
"amp_col = ndum + fg_half - fg_half_nmos\n",
"amp_ports = self.draw_mos_conn('nch', 0, amp_col, fg_amp, nsdir, nddir,\n",
" s_net=s_net, d_net=d_net)\n",
"load_ports = self.draw_mos_conn('pch', 0, load_col, fg_load, 2, 0,\n",
" s_net='', d_net='vout')\n",
"# amp_ports/load_ports are dictionaries of WireArrays representing\n",
"# transistor ports.\n",
"print(amp_ports)\n",
"print(amp_ports['g'])\n",
"```\n",
"Now we are ready to draw the actual transistor connections. To do so, we use the `draw_mos_conn()` function. As an example, `self.draw_mos_conn('pch', 0, load_col, fg_load, 2, 0)` creates an `AnalogMosConn` object on top of PMOS row 0, starting at transistor index `load_col` (with index 0 being left-most transistor), using `fg_load` fingers to the right, with source going up (code 2) and drain going down (code 0). Remember that the source/drain directions are used to determine gate wires location.\n",
"\n",
"The optional parameters `s_net` and `d_net` specify the net names of the source and drain of the transistor drawn, respectively. By default, if these are not specified (or set to empty strings), AnalogBase assume they connect to VDD for PMOS or VSS for NMOS. These parameters are used to infer dummy transistor schematic to simplify the process of generating LVS-clean schematics.\n",
"\n",
"the `draw_mos_conn()` method will return a dictionary from the strings `'g'`, `'d'`, and `'s'` to the `WireArray` objects for the corresponding vertical wires."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connecting Wires\n",
"```python\n",
"# create TrackID from relative track index\n",
"vin_tid = self.make_track_id('nch', 0, 'g', 0)\n",
"vout_tid = self.make_track_id('pch', 0, 'ds', 0)\n",
"vbias_tid = self.make_track_id('pch', 0, 'g', 0)\n",
"# can also convert from relative to absolute track index\n",
"print(self.get_track_index('nch', 0, 'g', 0))\n",
"\n",
"vin_warr = self.connect_to_tracks(amp_ports['g'], vin_tid)\n",
"vout_warr = self.connect_to_tracks([amp_ports[aout], load_ports['d']], vout_tid)\n",
"vbias_warr = self.connect_to_tracks(load_ports['g'], vbias_tid)\n",
"self.connect_to_substrate('ptap', amp_ports[aoutb])\n",
"self.connect_to_substrate('ntap', load_ports['s'])\n",
"```\n",
"This section used the `make_track_id()` and `get_track_index()` methods described before to get horizontal track indices from relative index. We then use `connect_to_tracks()` to connect wires to the desired tracks. `connect_to_substrate()` method is used to connect transistor junctions to the specified substrate contacts."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dummies and Pins\n",
"```python\n",
"vss_warrs, vdd_warrs = self.fill_dummy()\n",
"\n",
"self.add_pin('VSS', vss_warrs, show=show_pins)\n",
"self.add_pin('VDD', vdd_warrs, show=show_pins)\n",
"self.add_pin('vin', vin_warr, show=show_pins)\n",
"self.add_pin('vout', vout_warr, show=show_pins)\n",
"self.add_pin('vbias', vbias_warr, show=show_pins)\n",
"```\n",
"After all connections are made, the `fill_dummy()` method can be used to automatically connect all unconnected transistors to corresponding substrate contacts as dummy transistors. `add_pin()` function is used to add layout pins, as seem from the routing demo module."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Schematic Parameters\n",
"```python\n",
"# compute schematic parameters\n",
"self._sch_params = dict(\n",
" lch=lch,\n",
" w_dict=w_dict,\n",
" intent_dict=intent_dict,\n",
" fg_dict=fg_dict,\n",
" dum_info=self.get_sch_dummy_info(),\n",
")\n",
"```\n",
"Finally, we compute the schematic parameter dictionary, which will be used with the schematic generator later to produce LVS clean schematic. The `get_sch_dummy_info()` method will return a data structure that describes all the dummy transistors in this AnalogBase. This data structure will be used by the schematic generator to create the corresponding transistors."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## SF Amplifier Exercise\n",
"Now that you understand how the common-source amplifier layout generator works, try to complete the following source-follower amplifier class by filling in missing codes. The floorplan for the source-follower amplifier is drawn for you below:\n",
"<img src=\"bootcamp_pics/3_analogbase/analogbase_9.PNG\" alt=\"Drawing\" style=\"width: 400px;\"/>\n",
"Notice that:\n",
"* we have two rows of NMOS.\n",
"* Gate connection is on the top for second row\n",
"* To minimize parasitics, we will use leave 1 horizontal track empty between vin and VDD.\n",
"\n",
"You can evaluate the next cell (Press Ctrl+Enter) to see a preliminary layout of the source follower. It will also run LVS after generating the layout, which will fail if your layout is not correct."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"creating BagProject\n",
"computing layout\n",
"ext_w0 = 1, ext_wend=7, ytop=2880\n",
"ext_w0 = 2, ext_wend=9, ytop=3024\n",
"final: ext_w0 = 1, ext_wend=7, ytop=2880\n",
"creating layout\n",
"layout done\n",
"computing AMP_SF schematics\n"
]
},
{
"ename": "TypeError",
"evalue": "design() argument after ** must be a mapping, not NoneType",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-1-6182a9967843>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 178\u001b[0m \u001b[0mbprj\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mbag\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mBagProject\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 179\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 180\u001b[1;33m \u001b[0mdemo_core\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrun_flow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mbprj\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtop_specs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'amp_sf_soln'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mAmpSF\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrun_lvs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlvs_only\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;32m~/projects/bag_gen/BAG2_cds_ff_mpt/BAG_XBase_demo/xbase_demo/core.py\u001b[0m in \u001b[0;36mrun_flow\u001b[1;34m(prj, specs, dsn_name, lay_cls, sch_cls, run_lvs, lvs_only)\u001b[0m\n\u001b[0;32m 376\u001b[0m \u001b[1;31m# generate design/testbench schematics\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 377\u001b[0m gen_schematics(prj, specs, dsn_name, dsn_sch_params, sch_cls=sch_cls,\n\u001b[1;32m--> 378\u001b[1;33m check_lvs=run_lvs, lvs_only=lvs_only)\n\u001b[0m\u001b[0;32m 379\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 380\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mlvs_only\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32m~/projects/bag_gen/BAG2_cds_ff_mpt/BAG_XBase_demo/xbase_demo/core.py\u001b[0m in \u001b[0;36mgen_schematics\u001b[1;34m(prj, specs, dsn_name, sch_params, sch_cls, check_lvs, lvs_only)\u001b[0m\n\u001b[0;32m 99\u001b[0m \u001b[0mdsn\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mprj\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcreate_design_module\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msch_lib\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msch_cell\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 100\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'computing %s schematics'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mgen_cell\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 101\u001b[1;33m \u001b[0mdsn\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdesign\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0msch_params\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 102\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 103\u001b[0m dsn = prj.new_schematic_instance(lib_name=sch_lib, cell_name=sch_cell,\n",
"\u001b[1;31mTypeError\u001b[0m: design() argument after ** must be a mapping, not NoneType"
]
}
],
"source": [
"from abs_templates_ec.analog_core import AnalogBase\n",
"\n",
"\n",
"class AmpSF(AnalogBase):\n",
" \"\"\"A template of a single transistor with dummies.\n",
"\n",
" This class is mainly used for transistor characterization or\n",
" design exploration with config views.\n",
"\n",
" Parameters\n",
" ----------\n",
" temp_db : :class:`bag.layout.template.TemplateDB`\n",
" the template database.\n",
" lib_name : str\n",
" the layout library name.\n",
" params : dict[str, any]\n",
" the parameter values.\n",
" used_names : set[str]\n",
" a set of already used cell names.\n",
" kwargs : dict[str, any]\n",
" dictionary of optional parameters. See documentation of\n",
" :class:`bag.layout.template.TemplateBase` for details.\n",
" \"\"\"\n",
"\n",
" def __init__(self, temp_db, lib_name, params, used_names, **kwargs):\n",
" AnalogBase.__init__(self, temp_db, lib_name, params, used_names, **kwargs)\n",
" self._sch_params = None\n",
"\n",
" @property\n",
" def sch_params(self):\n",
" return self._sch_params\n",
"\n",
" @classmethod\n",
" def get_params_info(cls):\n",
" \"\"\"Returns a dictionary containing parameter descriptions.\n",
"\n",
" Override this method to return a dictionary from parameter names to descriptions.\n",
"\n",
" Returns\n",
" -------\n",
" param_info : dict[str, str]\n",
" dictionary from parameter name to description.\n",
" \"\"\"\n",
" return dict(\n",
" lch='channel length, in meters.',\n",
" w_dict='width dictionary.',\n",
" intent_dict='intent dictionary.',\n",
" fg_dict='number of fingers dictionary.',\n",
" ndum='number of dummies on each side.',\n",
" ptap_w='NMOS substrate width, in meters/number of fins.',\n",
" ntap_w='PMOS substrate width, in meters/number of fins.',\n",
" show_pins='True to draw pin geometries.',\n",
" )\n",
"\n",
" def draw_layout(self):\n",
" \"\"\"Draw the layout of a transistor for characterization.\n",
" \"\"\"\n",
"\n",
" lch = self.params['lch']\n",
" w_dict = self.params['w_dict']\n",
" intent_dict = self.params['intent_dict']\n",
" fg_dict = self.params['fg_dict']\n",
" ndum = self.params['ndum']\n",
" ptap_w = self.params['ptap_w']\n",
" ntap_w = self.params['ntap_w']\n",
" show_pins = self.params['show_pins']\n",
"\n",
" fg_amp = fg_dict['amp']\n",
" fg_bias = fg_dict['bias']\n",
"\n",
" if fg_bias % 2 != 0 or fg_amp % 2 != 0:\n",
" raise ValueError('fg_bias=%d and fg_amp=%d must all be even.' % (fg_bias, fg_amp))\n",
"\n",
" fg_half_bias = fg_bias // 2\n",
" fg_half_amp = fg_amp // 2\n",
" fg_half = max(fg_half_bias, fg_half_amp)\n",
" fg_tot = (fg_half + ndum) * 2\n",
"\n",
" nw_list = [w_dict['bias'], w_dict['amp']]\n",
" nth_list = [intent_dict['bias'], intent_dict['amp']]\n",
" ng_tracks = [1, 3]\n",
" nds_tracks = [1, 1]\n",
"\n",
" n_orient = ['R0', 'MX']\n",
"\n",
" self.draw_base(lch, fg_tot, ptap_w, ntap_w, nw_list,\n",
" nth_list, [], [],\n",
" ng_tracks=ng_tracks, nds_tracks=nds_tracks,\n",
" pg_tracks=[], pds_tracks=[],\n",
" n_orientations=n_orient,\n",
" )\n",
"\n",
" if (fg_amp - fg_bias) % 4 == 0:\n",
" s_net, d_net = 'VDD', 'vout'\n",
" aout, aoutb, nsdir, nddir = 'd', 's', 2, 0\n",
" else:\n",
" s_net, d_net = 'vout', 'VDD'\n",
" aout, aoutb, nsdir, nddir = 's', 'd', 0, 2\n",
"\n",
" # TODO: compute bias_col and amp_col\n",
" bias_col = amp_col = 0\n",
"\n",
" amp_ports = self.draw_mos_conn('nch', 1, amp_col, fg_amp, nsdir, nddir,\n",
" s_net=s_net, d_net=d_net)\n",
" bias_ports = self.draw_mos_conn('nch', 0, bias_col, fg_bias, 0, 2,\n",
" s_net='', d_net='vout')\n",
"\n",
" # TODO: get TrackIDs for horizontal tracks\n",
" # The following are related code copied and pasted from AmpCS\n",
" # for reference\n",
" # vin_tid = self.make_track_id('nch', 0, 'g', 0)\n",
" # vout_tid = self.make_track_id('pch', 0, 'ds', 0)\n",
" # vbias_tid = self.make_track_id('pch', 0, 'g', 0)\n",
" vdd_tid = vin_tid = vout_tid = vbias_tid = None\n",
"\n",
" if vdd_tid is None:\n",
" return\n",
"\n",
" # uncomment to visualize track location\n",
" # hm_layer = self.mos_conn_layer + 1\n",
" # xl = self.bound_box.left_unit\n",
" # xr = self.bound_box.right_unit\n",
" # self.add_wires(hm_layer, vdd_tid.base_index, xl, xr, unit_mode=True)\n",
" # self.add_wires(hm_layer, vin_tid.base_index, xl, xr, unit_mode=True)\n",
" # self.add_wires(hm_layer, vout_tid.base_index, xl, xr, unit_mode=True)\n",
" # self.add_wires(hm_layer, vbias_tid.base_index, xl, xr, unit_mode=True)\n",
" \n",
" # TODO: connect transistors to horizontal tracks\n",
" # The following are related code copied and pasted from AmpCS\n",
" # for reference\n",
" # vin_warr = self.connect_to_tracks(amp_ports['g'], vin_tid)\n",
" # vout_warr = self.connect_to_tracks([amp_ports[aout], load_ports['d']], vout_tid)\n",
" # vbias_warr = self.connect_to_tracks(load_ports['g'], vbias_tid)\n",
" vin_warr = vout_warr = vbias_warr = vdd_warr = None\n",
"\n",
" if vin_warr is None:\n",
" return\n",
"\n",
" self.connect_to_substrate('ptap', bias_ports['s'])\n",
"\n",
" vss_warrs, _ = self.fill_dummy()\n",
"\n",
" self.add_pin('VSS', vss_warrs, show=show_pins)\n",
" # TODO: add pins\n",
"\n",
" # set schematic parameters\n",
" self._sch_params = dict(\n",
" lch=lch,\n",
" w_dict=w_dict,\n",
" intent_dict=intent_dict,\n",
" fg_dict=fg_dict,\n",
" dum_info=self.get_sch_dummy_info(),\n",
" )\n",
"\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",
"from xbase_demo.demo_layout.core import AmpSFSoln\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.run_flow(bprj, top_specs, 'amp_sf_soln', AmpSF, run_lvs=True, lvs_only=True)"
]
}
],
"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.5"
}
},
"nbformat": 4,
"nbformat_minor": 1
}