{"id":50865752,"url":"https://github.com/medwatt/gmid","last_synced_at":"2026-06-15T01:04:02.480Z","repository":{"id":155318812,"uuid":"571324622","full_name":"medwatt/gmid","owner":"medwatt","description":"Python script for generating lookup tables for the gm/ID design methodology and much more ...","archived":false,"fork":false,"pushed_at":"2026-05-28T09:28:36.000Z","size":5154,"stargazers_count":129,"open_issues_count":1,"forks_count":27,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-05-28T11:15:04.422Z","etag":null,"topics":["analog-design","circuit-simulation","digital-design","gmid","mosfet","mosfet-transistor"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/medwatt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-11-27T21:21:17.000Z","updated_at":"2026-05-28T09:28:40.000Z","dependencies_parsed_at":"2025-03-25T14:35:24.708Z","dependency_job_id":"90b86789-ef83-4178-a46d-b70d3fd7ca16","html_url":"https://github.com/medwatt/gmid","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/medwatt/gmid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medwatt%2Fgmid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medwatt%2Fgmid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medwatt%2Fgmid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medwatt%2Fgmid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/medwatt","download_url":"https://codeload.github.com/medwatt/gmid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/medwatt%2Fgmid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34343313,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analog-design","circuit-simulation","digital-design","gmid","mosfet","mosfet-transistor"],"created_at":"2026-06-15T01:04:01.602Z","updated_at":"2026-06-15T01:04:02.472Z","avatar_url":"https://github.com/medwatt.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mosplot: MOSFET Characterization in Python\n\n**Mosplot** is a Python tool for the gm/Id design methodology. It uses a SPICE\nor Spectre simulator to build lookup tables of MOSFET operating-point\nparameters, then lets you plot, interpolate, and size analog circuits from\nthose tables.\n\n## Table of Contents\n\n1. [Installation](#installation)\n2. [Generating a Lookup Table](#generating-a-lookup-table)\n   - [Step 1; Configure a Simulator](#step-1--configure-a-simulator)\n   - [Step 2; Define Transistor Sweeps](#step-2--define-transistor-sweeps)\n   - [Step 3; Build the Table](#step-3--build-the-table)\n3. [Using a Lookup Table](#using-a-lookup-table)\n4. [Plotting](#plotting)\n   - [Built-in Expressions](#built-in-expressions)\n   - [plot_by_expression](#plot_by_expression)\n   - [plot_by_sweep](#plot_by_sweep)\n   - [quick_plot](#quick_plot)\n5. [Interpolation \u0026 Raw Lookups](#interpolation--raw-lookups)\n6. [Optimization](#optimization)\n7. [Acknowledgments](#acknowledgments)\n\n## Installation\n\n**Requirements:** Python 3.9+, numpy, scipy, matplotlib, and at least one\nsupported simulator (ngspice, hspice, or Spectre).\n\n```bash\npip install git+https://github.com/medwatt/gmid.git\n```\n\nFor a local editable install:\n\n```bash\ngit clone https://github.com/medwatt/gmid.git\ncd gmid\npip install -e .\n```\n\n## Generating a Lookup Table\n\nBuilding a lookup table takes three steps: configure a simulator, define the\nsweep ranges, then run the builder.\n\n### Step 1:  Configure a Simulator\n\nPick one of the three supported simulators. All share the same parameter set;\nonly the class name differs.\n\n#### ngspice\n\n```python\nfrom mosplot.lookup_table_generator.simulators import NgspiceSimulator\n\nsim = NgspiceSimulator(\n    # Path to the simulator binary (or just \"ngspice\" if it is on PATH).\n    simulator_path=\"ngspice\",\n\n    # Simulation temperature in °C.\n    temperature=27,\n\n    # At least one of include_paths or lib_mappings must be provided.\n    include_paths=[\n        \"./NMOS_VTH.inc\",\n        \"./PMOS_VTH.inc\",\n    ],\n    # lib_mappings=[(\"/path/to/models.lib\", \"tt_pre\")],\n\n    # Parameters to extract. Defaults to all available if omitted.\n    # Only the parameters listed here appear in the generated netlists, so\n    # omit anything your PDK's models don't expose (see note below).\n    parameters_to_save=[\"id\", \"vth\", \"vdsat\", \"gm\"],\n\n    # Transistor instance name used in the netlist.\n    mos_spice_symbols=(\"m1\", \"m1\"),\n\n    # Device width, or any other model-specific sizing parameter.\n    device_parameters={\"w\": 10e-6},\n\n    # For models compiled with OpenVAF (.osdi):\n    # osdi_paths=[\"./path/to/model.osdi\"],\n\n    # Extra SPICE lines appended verbatim to the netlist:\n    # raw_spice=[\"line 1\", \"line 2\"],\n)\n```\n\n#### hspice\n\n```python\nfrom mosplot.lookup_table_generator.simulators import HspiceSimulator\n\nsim = HspiceSimulator(\n    simulator_path=\"hspice\",\n    temperature=27,\n    lib_mappings=[(\"/tools/pdk/models/hspice/design_wrapper.lib\", \"tt_pre\")],\n    parameters_to_save=[\"id\", \"vth\", \"vdsat\", \"gm\"],\n\n    # When the transistor is wrapped in a subcircuit, set the first entry\n    # to the instance name and the second to the hierarchical node name.\n    mos_spice_symbols=(\"x1\", \"x1.main\"),\n\n    device_parameters={\"w\": 10e-6},\n)\n```\n\n#### Spectre\n\n```python\nfrom mosplot.lookup_table_generator.simulators import SpectreSimulator\n\nsim = SpectreSimulator(\n    simulator_path=\"spectre\",\n    temperature=27,\n    lib_mappings=[(\"/tools/pdk/models/spectre/design.scs\", \"tt\")],\n    parameters_to_save=[\"id\", \"vth\", \"vdsat\", \"gm\"],\n    mos_spice_symbols=(\"M1\", \"M1\"),\n    device_parameters={\"w\": 10e-6},\n)\n```\n\n\u003e [!TIP]\n\u003e **If a parameter is not available in your PDK, omit it -- don't fork this\n\u003e repo just for that.** Not every model exposes every operating-point parameter\n\u003e (for example, `vdssat` exists on BSIM4 models but not on many others).\n\u003e Anything you leave out of `parameters_to_save` is left out of the generated\n\u003e netlists entirely, so the simulation runs cleanly on models that lack it.\n\u003e Requesting a name the backend doesn't support prints a warning listing the\n\u003e supported names. Plotting and lookups only require `id` and `gm` (the\n\u003e gm/Id grid is built from them); everything else is optional, and the tool\n\u003e adapts to whatever the table contains (e.g. `vdsat` vs `vdssat` is\n\u003e detected automatically).\n\n### Step 2: Define Transistor Sweeps\n\n```python\nfrom mosplot.lookup_table_generator import TransistorSweep\n\nnmos_sweep = TransistorSweep(\n    mos_type=\"nmos\",\n    vgs=(0, 1.0, 0.01),    # (start, stop, step)\n    vds=(0, 1.0, 0.01),\n    vbs=(0, -1.0, -0.1),\n    length=[45e-9, 100e-9],\n)\n\npmos_sweep = TransistorSweep(\n    mos_type=\"pmos\",\n    vgs=(0, -1.0, -0.01),\n    vds=(0, -1.0, -0.01),\n    vbs=(0, 1.0, 0.1),\n    length=[200e-9, 500e-9],\n)\n```\n\n### Step 3: Build the Table\n\n```python\nfrom mosplot.lookup_table_generator import LookupTableGenerator\n\ngen = LookupTableGenerator(\n    description=\"freepdk 45nm\",\n    simulator=sim,\n    model_sweeps={\n        \"NMOS_VTH\": nmos_sweep,\n        \"PMOS_VTH\": pmos_sweep,\n    },\n    # Number of parallel worker processes.\n    # For ngspice, keep this at 1; ngspice already parallelises internally\n    # and adding outer parallelism tends to slow things down.\n    n_process=1,\n)\n\n# Optional: run a quick DC operating-point simulation to verify the setup\n# before committing to a full sweep.\n# gen.op_simulation()\n\n# Run the sweep and write the table to disk as a .npz file.\ngen.build(\"./freepdk_45nm\")\n```\n\n## Using a Lookup Table\n\n```python\nimport numpy as np\nfrom mosplot.plot import load_lookup_table, Mosfet, Expression\n```\n\nLoad the table:\n\n```python\nlookup_table = load_lookup_table(\"path/to/lookup-table.npz\")\n```\n\n\u003e **Tip:** Work in a REPL or Jupyter notebook so you load the table once and\n\u003e reuse it across multiple queries without waiting for the file to reload.\n\nThe table is a plain Python dict:\n\n```python\nprint(lookup_table.keys())\n# dict_keys(['nch_lvt', 'pch_lvt', 'width', 'description', 'simulator', 'parameter_names'])\n```\n\nCreate `Mosfet` instances by fixing the bias point. All later queries operate\non this filtered view:\n\n```python\nnmos = Mosfet(lookup_table=lookup_table, mos=\"nch_lvt\", vbs=0.0, vds=0.6,  vgs=(0.01, 1.10))\npmos = Mosfet(lookup_table=lookup_table, mos=\"pch_lvt\", vbs=0.0, vds=-0.6, vgs=(-1.2, -0.01))\n```\n\n`vds` and `vbs` must be scalar. `vgs` as a `(min, max)` tuple filters\nto that range; omit it to use all values in the table. To see the available\nlengths:\n\n```python\nprint(nmos.length)\n# array([6.50e-08, 8.00e-08, 1.00e-07, ...])\n```\n\n## Plotting\n\n### Built-in Expressions\n\nPass any of these to `x_expression` / `y_expression` in a plot or lookup call:\n\n\n | Expression                     | Description                   |\n | ---                            | ---                           |\n | `gmid_expression`              | $g_m / I_D$                   |\n | `vgs_expression`               | $V_{GS}$                      |\n | `vds_expression`               | $V_{DS}$                      |\n | `vbs_expression`               | $V_{BS}$                      |\n | `gain_expression`              | Intrinsic gain $g_m / g_{ds}$ |\n | `current_density_expression`   | $I_D / W$                     |\n | `transit_frequency_expression` | Transit frequency $f_T$       |\n | `early_voltage_expression`     | Early voltage $V_A$           |\n\n\nTo define a custom expression:\n\n```python\nExpression(\n    variables=[\"id\", \"gds\"],\n    function=lambda x, y: x / y,\n    label=\"$I_D / g_{ds}$\",\n)\n```\n\n### plot_by_expression\n\nPlots across the filtered `vgs` range, with one curve per selected length.\n\n**$I_D/W$ vs $g_m/I_D$:**\n\n```python\nnmos.plot_by_expression(\n    x_expression=nmos.gmid_expression,\n    y_expression=nmos.current_density_expression,\n    filtered_values=nmos.length[0:-1:4],   # every 4th length\n    y_scale=\"log\",\n    save_fig=\"./figures/nmos_current_density.svg\",\n)\n```\n\n![current density plot](./figures/nmos_current_density.svg)\n\n**Custom expression on the y-axis:**\n\n```python\nnmos.plot_by_expression(\n    x_expression=nmos.vgs_expression,\n    y_expression=Expression(\n        variables=[\"id\", \"gds\"],\n        function=lambda x, y: x / y,\n        label=\"$I_D / g_{ds}$ (A/S)\",\n    ),\n    filtered_values=nmos.length[0:-1:4],\n)\n```\n\n![custom expression](./figures/nmos_custom_expression.svg)\n\n**Adding a second y-axis** with `y2_expression`:\n\n```python\nnmos.plot_by_expression(\n    x_expression=nmos.gmid_expression,\n    y_expression=nmos.transit_frequency_expression,\n    y2_expression=nmos.gain_expression,\n    filtered_values=nmos.length[0:-1:4],\n    y_scale=\"log\",\n    save_fig=\"./figures/nmos_twin_plot.svg\",\n)\n```\n\n![twin plots](./figures/nmos_twin_plot.svg)\n\n### plot_by_sweep\n\nUses the full lookup table rather than the filtered instance. Useful for I-V\ncurves and length sweeps.\n\n**Input characteristic** ($I_D$ vs $V_{GS}$):\n\n```python\nnmos.plot_by_sweep(\n    length=nmos.length[:-1:4],\n    vbs=0, vds=0.6,\n    vgs=(0.01, 1.2, 0.01),\n    x_expression=nmos.vgs_expression,\n    y_expression=nmos.id_expression,\n    primary=\"vgs\",\n    y_scale=\"log\",\n)\n```\n\n![input characteristic](./figures/nmos_id_vs_vgs.svg)\n\n**Output characteristic** ($I_D$ vs $V_{DS}$):\n\n```python\nnmos.plot_by_sweep(\n    length=65e-9,\n    vbs=0,\n    vds=(0.0, 1.2, 0.01),\n    vgs=(0.0, 1.2, 0.2),\n    x_expression=nmos.vds_expression,\n    y_expression=nmos.id_expression,\n    primary=\"vds\",\n)\n```\n\n![output characteristic](./figures/nmos_id_vs_vds.svg)\n\n**Speed and gain vs length:**\n\n```python\nnmos.plot_by_sweep(\n    length=nmos.length[1:],\n    vbs=0, vds=1.2,\n    vgs=(0.4, 1.2, 0.25),\n    x_expression=nmos.length_expression,\n    y_expression=nmos.transit_frequency_expression,\n    y2_expression=nmos.gain_expression,\n    primary=\"length\",\n    y_scale=\"log\", y2_scale=\"linear\",\n)\n```\n\n![speed and gain vs length](./figures/nmos_twin_plot_ft_gain.svg)\n\n### quick_plot\n\nOverlay arbitrary data arrays into a single plot; useful when you want to\ncompare quantities that share an x-axis but don't come from a single sweep.\n\n**Extract the data:**\n\n```python\nvdsat, vov, vstar = nmos.lookup_expression_from_table(\n    length=100e-9, vbs=0, vds=0.6,\n    vgs=(0.01, 1.2, 0.01),\n    primary=\"vgs\",\n    expression=[\n        nmos.vdsat_expression,\n        Expression(variables=[\"vgs\", \"vth\"], function=lambda x, y: x - y),\n        Expression(variables=[\"gm\",  \"id\"],  function=lambda x, y: 2 / (x / y)),\n    ],\n)\n```\n\n**Plot:**\n\n```python\nx_values = np.arange(0.01, 1.2 + 0.01, 0.01)\n\nnmos.quick_plot(\n    x=[x_values, x_values, x_values],\n    y=[vdsat, vstar, vov],\n    legend=[\"$V_{\\\\mathrm{DS}_{\\\\mathrm{SAT}}}$\", \"$V^{\\\\star}$\", \"$V_{\\\\mathrm{OV}}$\"],\n    x_limit=(0.1, 1),\n    y_limit=(0, 0.6),\n    x_label=\"$V_{\\\\mathrm{GS}}$\",\n    y_label=\"$V$\",\n    save_fig=\"./figures/nmos_quick_plot.svg\",\n)\n```\n\n![quick plot](./figures/nmos_quick_plot.svg)\n\n## Interpolation \u0026 Raw Lookups\n\n**Interpolate at a single point**; given a length and a gm/Id target, find the\ncorresponding gain:\n\n```python\nx = nmos.interpolate(\n    x_expression=nmos.length_expression,\n    x_value=100e-9,\n    y_expression=nmos.gmid_expression,\n    y_value=15,\n    z_expression=nmos.gain_expression,\n)\n```\n\n**Interpolate over a 2D sweep:**\n\n```python\nx = nmos.interpolate(\n    x_expression=nmos.vdsat_expression,\n    x_value=(0.08, 0.12, 0.01),\n    y_expression=nmos.gds_expression,\n    y_value=(1e-6, 4e-6, 1e-6),\n    z_expression=nmos.gmid_expression,\n)\n```\n\n**Direct table lookup (no interpolation):**\n\n```python\nx = nmos.lookup_expression_from_table(\n    length=65e-9,\n    vbs=0,\n    vds=(0.0, 1.2, 0.01),   # primary sweep variable\n    vgs=(0.0, 1.2, 0.2),    # secondary; omit to use all table values\n    primary=\"vds\",\n    expression=nmos.current_density_expression,\n)\n```\n\n## Optimization\n\nDefine the free parameters and target specifications, implement a `Circuit`\nclass that maps them to performance metrics, then run the optimizer:\n\n```python\nfrom mosplot.optimizer import Optimizer, DesignReport\nfrom datatypes import Spec, OptimizationParameter\n\nparameters = [\n    OptimizationParameter(\"L_input\",    (100e-9, 2e-6)),\n    OptimizationParameter(\"gmid_input\", (7, 16)),\n    OptimizationParameter(\"L_load\",     (100e-9, 2e-6)),\n    OptimizationParameter(\"gmid_load\",  (7, 15)),\n    OptimizationParameter(\"L_tail\",     (100e-9, 2e-6)),\n    OptimizationParameter(\"gmid_tail\",  (7, 15)),\n    OptimizationParameter(\"Ibias\",      (1e-6, 40e-6)),\n]\n\ntarget_specs = {\n    \"GBW\":       Spec(5e6,    \"max\", 5),\n    \"Gain\":      Spec(30,     \"max\", 2),\n    \"Ibias\":     Spec(20e-6,  \"min\", 1),\n    \"ICMR_LOW\":  Spec(0.1,    \"min\", 1),\n    \"ICMR_HIGH\": Spec(0.7,    \"max\", 5),\n    \"CMRR\":      Spec(2000,   \"max\", 3),\n    \"Area\":      Spec(15e-12, \"min\", 1),\n}\n\ncircuit = Circuit(lookup_table, pmos_range, nmos_range, \"pch_lvt\", \"nch_lvt\", VDD, CL)\noptimizer = Optimizer(circuit, parameters, target_specs)\noptimizer.optimize(maxiter=5)\n\nreport = DesignReport(circuit, optimizer)\nprint(report.report())\n```\n\n## Acknowledgments\n\n- HSPICE output parsing is based on [this script](https://github.com/HMC-ACE/hspiceParser).\n\n- If you find this tool useful, please cite it:\n\n    ```bibtex\n    @misc{medwatt_mosplot,\n        author       = {Mohamed Watfa},\n        title        = {{Mosplot: The MOSFET Characterization Tool}},\n        month        = mar,\n        year         = 2025,\n        publisher    = {GitHub},\n        journal      = {GitHub repository},\n        howpublished = {\\url{https://github.com/medwatt/gmid}},\n        note         = {Accessed: 2025-03-31}\n    }\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmedwatt%2Fgmid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmedwatt%2Fgmid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmedwatt%2Fgmid/lists"}