Commit 05246466 authored by Mario Krattenmacher's avatar Mario Krattenmacher
Browse files

Merge branch '14-add-simulation-options-to-docudutlib' into 'main'

Resolve "Add simulation options to DocuDutLib"

Closes #14

See merge request !15
parents 4f37b7db 980d2178
Loading
Loading
Loading
Loading
+91 −18
Original line number Diff line number Diff line
@@ -26,7 +26,17 @@ from typing import Dict, List, Mapping, Sequence, Optional, Union
from joblib import Parallel, delayed
from scipy import interpolate
from DMT.config import COMMAND_TEX, DATA_CONFIG
from DMT.core import DutType, DutLib, specifiers, sub_specifiers, specifiers_ss_para
from DMT.core import (
    DutType,
    DutLib,
    specifiers,
    sub_specifiers,
    MCard,
    DutCircuit,
    DutMeas,
    Sweep,
    SimCon,
)
from DMT.core.plot import MIX, PLOT_STYLES, natural_scales, Plot
from DMT.external.os import recursive_copy, rmtree

@@ -307,7 +317,10 @@ class DocuDutLib(object):
        self,
        dut_lib: DutLib,
        devices: Optional[Sequence[Mapping[str, object]]] = None,
        date=None,
        date: Optional[str] = None,
        modelcard_dict: Optional[Dict[DutType, MCard]] = None,
        DutCircuitClass: Optional[DutCircuit] = None,
        dut_class_kwargs: Optional[Dict] = None,
    ):
        self.dut_lib = dut_lib

@@ -354,6 +367,31 @@ class DocuDutLib(object):

        self.plts: List[Plot] = []

        self.modelcard_dict = modelcard_dict
        self.DutCircuitClass = DutCircuitClass
        if dut_class_kwargs is None:
            self.dut_class_kwargs = {}
        else:
            self.dut_class_kwargs = dut_class_kwargs

    def get_dut_sim(self, dut_meas: DutMeas) -> DutCircuit:
        """Retrieve a circuit dut view which should be compared to the given dut_meas"""
        if self.DutCircuitClass is not None:
            return self.DutCircuitClass(
                database_dir=None,
                dut_type=dut_meas.dut_type,
                input_circuit=self.modelcard_dict[dut_meas.dut_type],
                technology=dut_meas.technology,  # needed for scaling!
                width=dut_meas.width,
                length=dut_meas.length,
                contact_config=dut_meas.contact_config,
                nfinger=dut_meas.nfinger,
                reference_node=dut_meas.reference_node,
                **self.dut_class_kwargs,
            )
        else:
            return None

    def generate_docu(
        self,
        target_base_path: Union[str, Path],
@@ -376,16 +414,17 @@ class DocuDutLib(object):
        """
        self.create_all_plots(plot_specs)

        # TODO
        # technology -> one lib can have more than one technology...
        # mcard
        # ADD data from mcard simulation

        if show and len(self.plts) > 1:
            for plt in self.plts[:-1]:
        if show and self.plts:
            for plt in self.plts:
                plt.plot_pyqtgraph(show=False)

            self.plts[-1].plot_pyqtgraph(show=True)
            self.plts[0].show_pyqtgraph()

            for plt in self.plts:
                plt.mw_pg = None
                plt.pw_pg = None
                # remove the plot from the object to allow Parallel prcessing
                # pickle is used to transfer between processes

        self.generate_all_plots(target_base_path, save_tikz_settings)

@@ -415,6 +454,7 @@ class DocuDutLib(object):
                    'ymax'        : 300,         #optional, float: Maximum Value on y-axis to be displayed
                    'ymin'        : 0,           #optional, float: Minimum Value on y-axis to be displayed
                    'no_at'       : True,        #optional, Bool: if True, do not display the "at" quantities in the legend.
                    'simulate'    : True,        #optional, Bool: if True, a matching simulation is added to the plot, if not given, default is True
                },

        """
@@ -424,6 +464,7 @@ class DocuDutLib(object):
            "dut_type": DutType.npn,
            "no_at": False,  # no at_specifier= in legend
        }
        sim_con = SimCon()

        # set defaults
        for plot_spec in plot_specs:
@@ -441,9 +482,7 @@ class DocuDutLib(object):
            valid_plots = PLOT_DEFAULTS[plot_spec["dut_type"]].keys()
            if not plot_type in valid_plots:
                raise IOError(
                    "Plot type "
                    + plot_type
                    + " not valid. Valid: "
                    f"Plot type {plot_type} not valid. Valid: "
                    + " ".join(str(PLOT_DEFAULTS.keys()))
                    + " ."
                )
@@ -452,7 +491,7 @@ class DocuDutLib(object):
                raise IOError("Plot style not valid. Valid: " + " ".join(PLOT_STYLES))

            if not "key" in plot_spec.keys():
                raise IOError("Database key not specified for plot of type " + plot_type + " .")
                raise IOError(f"Database key not specified for plot of type {plot_type}.")

            # matching key?
            if "exact_match" not in plot_spec.keys():
@@ -476,14 +515,20 @@ class DocuDutLib(object):
        self.plts = []
        for plot_spec in plot_specs:
            plot_type = plot_spec["type"]
            print("Generating plots of type " + plot_type + " .")
            print(f"Generating plots of type {plot_type}.")

            style = plot_spec["style"]
            print("Chosen plot style: " + style)
            print(f"Chosen plot style: {style}")

            for dut in self.duts:
                if dut.dut_type != plot_spec["dut_type"]:
                    continue
                if plot_spec.get(
                    "simulate", PLOT_DEFAULTS[dut.dut_type][plot_type].get("simulate", True)
                ):
                    dut_sim = self.get_dut_sim(dut)
                else:
                    dut_sim = None

                quantity_x = plot_spec.get(
                    "quantity_x", PLOT_DEFAULTS[dut.dut_type][plot_type]["quantity_x"]
@@ -549,8 +594,8 @@ class DocuDutLib(object):
                    except AttributeError:
                        at_scale_ = natural_scales[at_]

                    if at_ == specifiers.CURRENT + "B" or at_ == specifiers.CURRENT_DENSITY + "B":
                        at_scale_ = at_scale_ * 1e3
                    # if at_ == specifiers.CURRENT + "B" or at_ == specifiers.CURRENT_DENSITY + "B":
                    #     at_scale_ = at_scale_ * 1e3

                    at_scale.append(at_scale_)

@@ -576,6 +621,9 @@ class DocuDutLib(object):
                        temp, plot_spec[specifiers.TEMPERATURE]
                    ):
                        temps.append(dut.get_key_temperature(key))
                    elif specifiers.TEMPERATURE not in plot_spec:
                        temps.append(dut.get_key_temperature(key))

                temps = list(set(temps))

                for temp in temps:
@@ -762,6 +810,31 @@ class DocuDutLib(object):
                                    peaks["jc"].append(np.tile(jc_peak, 10))
                                    peaks["vbc"].append(np.tile(point[0], 10))

                                if dut_sim is not None:
                                    # get a sweep
                                    sweep = Sweep.get_sweep_from_dataframe(
                                        data=df_tmp,
                                        temperature=temp,
                                        outputdef=[quantity_x, quantity_y],
                                        # othervar={},
                                    )
                                    # simulate
                                    sim_con.append_simulation(dut=dut_sim, sweep=sweep)
                                    sim_con.run_and_read()
                                    # add to plot
                                    df_sim = dut_sim.get_data(sweep=sweep)
                                    df_sim.ensure_specifier_column(
                                        quantity_x, area=AE0_drawn, ports=dut.ac_ports
                                    )
                                    df_sim.ensure_specifier_column(
                                        quantity_y, area=AE0_drawn, ports=dut.ac_ports
                                    )
                                    plt.add_data_set(
                                        df_sim[quantity_x].to_numpy(),
                                        df_sim[quantity_y].to_numpy(),
                                        label=label + " sim",
                                    )

                            plt.x_limits = x_limits
                            plt.y_limits = y_limits

+3 −2
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ Features:
* Clear syntax and definition to create a well described simulation independent of the simulator interface.

"""

# DMT_core
# Copyright (C) from 2022  SemiMod
# Copyright (C) until 2021  Markus Müller, Mario Krattenmacher and Pascal Kuthe
@@ -57,9 +58,9 @@ def get_sweepdef(
    data : DataFrame
        Frame to extract the sweepdefs from
    inner_sweep_voltage : SpecifierStr | None, optional
        A specifier that determiens the inner sweep voltage, by default None
        A specifier that determins the inner sweep voltage, by default None
    outer_sweep_voltage : SpecifierStr | None, optional
        A specifier that determiens the inner outer voltage, by default None
        A specifier that determins the inner outer voltage, by default None
    outer_sweep_voltage : SpecifierStr | None, optional
        A voltage at a third possible contact, that is not swept but may differ from zero.
    decimals_potentials : int, optional
+225 −0
Original line number Diff line number Diff line
""" Test case for DocuDutLib with simulation comparisions

I know.... the simulation data and the measurement data do not align!! They are from two different technologies, but those are the one we have currently at hand.

"""

import types
import logging
import shutil
from pathlib import Path
from DMT.config import COMMANDS
from DMT.core import (
    DutMeas,
    DutType,
    DocuDutLib,
    DutLib,
    specifiers,
    sub_specifiers,
    Technology,
    MCard,
)
from DMT.core.dut_view import DutView
from DMT.ngspice import DutNgspice
from test_dut_ngspice_osdi import get_circuit

folder_path = Path(__file__).resolve().parent
test_path = folder_path.parent
tmp_path = test_path / "tmp"
test_data_path = test_path / "test_core_no_interfaces" / "test_data"
# -->Start main function
# --->Setup for log
logging.basicConfig(
    level=logging.DEBUG,
    format="%(levelname)s - %(message)s",
    filename=folder_path.parent.parent / "logs" / "test_docudutlib_simu.log",
    filemode="w",
)


class TechDummy(Technology):
    def __init__(self):
        super().__init__(name="dummy")

    def scale_modelcard(
        self,
        mcard: MCard,
        lE0: float,
        bE0: float,
        nfinger: int,
        config: str,
        dut: DutView = None,
        lE_drawn_ref: float = None,
        bE_drawn_ref: float = None,
    ) -> MCard:
        # nothing to do here
        return mcard

    @staticmethod
    def deserialize():
        return TechDummy()

    def serialize(self):
        return {
            "class": str(self.__class__),
            "args": [],
            "kwargs": {},
            "constructor": "deserialize",
        }


def create_lib():
    """Create a dut lib."""

    # -->Define subroutine at first
    # --->Subroutine: filter_dut
    def filter_dut(dut_name):
        if "dummies" in dut_name:
            return None
        elif "TLM" in dut_name:
            return None
        elif dut_name == "":
            return None
        else:
            dut_transistor = DutMeas(
                database_dir=tmp_path,
                dut_type=DutType.npn,
                force=True,
                wafer=96,
                die="y",
                width=float(0.25e-6),
                length=float(0.25e-6),
                contact_config="CBEBC",
                name="dut_meas_npn",
                reference_node="E",
                technology=TechDummy(),
            )

            return dut_transistor

    # --->Arrange DMT class to dmt
    lib = DutLib(
        save_dir=tmp_path / "dut_lib_save",
        force=True,
        AC_filter_names=[("freq_vbc", "ac")],
        DC_filter_names=[("fgummel", "dc")],
    )
    # --->Add source measurement information in dmt and to duts
    lib.import_directory(
        import_dir=test_data_path,
        dut_filter=filter_dut,
        dut_level=1,
        force=True,
    )
    lib.dut_ref = lib.duts[0]

    # --->Add the open and short dummies to the lib
    dut_short = DutMeas(
        database_dir=tmp_path,
        name="dut_short_npn",
        dut_type=DutType.deem_short_bjt,
        reference_node="E",
        force=True,
        wafer=96,
        die="y",
        width=float(0.25e-6),
        length=float(0.25e-6),
        technology=TechDummy(),
    )
    dut_short.add_data(test_data_path / "dummy_short_freq.mdm", key="ac")
    dut_short.add_data(test_data_path / "short_dc.mdm", key="dc")
    dut_open = DutMeas(
        database_dir=tmp_path,
        name="dut_open_npn",
        dut_type=DutType.deem_open_bjt,
        reference_node="E",
        force=True,
        wafer=96,
        die="y",
        width=float(0.25e-6),
        length=float(0.25e-6),
        technology=TechDummy(),
    )
    dut_open.add_data(test_data_path / "dummy_open_freq.mdm", key="ac")
    lib.add_duts([dut_short, dut_open])

    # --->Clean the names of all dataframes, e.g. VB=>V_B
    for dut_a in lib:
        dut_a.clean_data(
            fallback={
                "S_DEEMB(1,1)": None,
                "S_DEEMB(2,1)": None,
                "S_DEEMB(1,2)": None,
                "S_DEEMB(2,2)": None,
            }
        )

    # emulate forced specifiers
    col_vb = specifiers.VOLTAGE + "B"
    col_ve = specifiers.VOLTAGE + "E"
    col_vc = specifiers.VOLTAGE + "C"
    for key in lib.dut_ref.data.keys():
        lib.dut_ref.data[key][col_vb + sub_specifiers.FORCED] = lib.dut_ref.data[key][col_vb]
        lib.dut_ref.data[key][col_vc + sub_specifiers.FORCED] = lib.dut_ref.data[key][col_vc]
        lib.dut_ref.data[key][col_ve + sub_specifiers.FORCED] = lib.dut_ref.data[key][col_ve]

    return lib


def create_mcard():
    mc_D21 = MCard(
        ["C", "B", "E", "S", "T"],
        default_module_name="",
        default_subckt_name="",
        va_file=folder_path.parent
        / "test_core_no_interfaces"
        / "test_va_code"
        / "hicuml2"
        / "hicumL2V2p4p0_release.va",
    )
    mc_D21.load_model_parameters(
        folder_path.parent
        / "test_core_no_interfaces"
        / "test_modelcards"
        / "IHP_ECE704_03_para_D21.mat",
    )
    mc_D21.update_from_vae(remove_old_parameters=True)
    mc_D21.get_circuit = types.MethodType(get_circuit, mc_D21)
    return mc_D21


def test_docu_with_sim():
    COMMANDS["openvaf"] = "openvaf"

    lib_test = create_lib()
    docu = DocuDutLib(
        lib_test,
        modelcard_dict={DutType.npn: create_mcard()},
        DutCircuitClass=DutNgspice,
        dut_class_kwargs={
            "get_circuit_arguments": {"use_build_in": False},
            "command_openvaf": "openvaf",
        },
    )
    docu_path = tmp_path / "docu_dut_lib_sim"
    docu.generate_docu(
        docu_path,
        plot_specs=[{"type": "gummel_vbc", "key": "fgummel", "style": "xtraction_color"}],
        show=False,  # not possible in CI/CD
        save_tikz_settings={
            "width": "3in",
            "height": "5in",
            "standalone": True,
            "svg": False,
            "build": False,  # not possible in CI/CD
            "mark_repeat": 20,
            "clean": False,  # Remove all files except *.pdf files in plots
        },
    )

    shutil.rmtree(docu_path)


if __name__ == "__main__":
    mcard = create_mcard()
    lib_test = test_docu_with_sim()
+1 −3
Original line number Diff line number Diff line
""" test ngspice input file generation.
"""

import types
import copy
from pathlib import Path
from DMT.config import COMMANDS

COMMANDS["OPENVAF"] = "openvaf"
from DMT.core import DutType, Sweep, specifiers, SimCon, Plot, MCard
from DMT.core.circuit import (
    Circuit,