diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..74aebab --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Arm Limited or its affiliates. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index ab0237d..2505a7e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,133 @@ -# mram_simulation_framework + +* [Arm's MRAM Simulation/Characterization Framework](#arms-mram-simulationcharacterization-framework) + * [Authors and Related Publications](#authors-and-related-publications) + * [Quick Start & More info](#quick-start--more-info) + * [Files organization](#files-organization) + * [s-LLGS Solvers](#s-llgs-solvers) + * [No-thermal or emulated-thermal simulations](#no-thermal-or-emulated-thermal-simulations) + * [Thermal Stochastic Simulations](#thermal-stochastic-simulations) + + +# Arm's MRAM Simulation/Characterization Framework + +## Authors and Related Publications + +* Fernando Garcia Redondo, fernando.garciaredondo@arm.com +* Pranay Prabhat +* Mudit Bhargava +Thanks to Cyrille Dray and Milos Milosavljevic for his helpful discussions. + +The following frameworks have been presented at +* ***A Compact Model for Scalable MTJ Simulation***, IEEE International Conference on Synthesis, Modeling, Analysis and Simulation Methods and Applications to Circuit Design, SMACD 2021. [link to manuscript](https://arxiv.org/abs/2106.04976) +* ***A Fokker-Planck Solver to Model MTJ Stochasticity*** European Solid-State Device Research Conference, ESSDERC 2021. + +This repository contains a framework for the characterization +and analysis of MRAM behavior including stochasticity, +and a compact model and framework +for the efficient and scalable simulation of circuits with MRAMs. + +We provide Verilog-A and Python compact models, able to emulate the behavior of +MRAMs switching at significant statistic events. +To calibrate the models for such stochastic based events, +we implemented and analyzed two FPE solvers (numerical FVM and analytical), and +presented an optimization module that orchestrates the efficient computation +of MRAM statistics and parameter regression. + +## Quick Start & More info +Summary: +* `test_sllgs_solver.py` shows you the basic s-LLGS solver config, calling (`sllgs_solver.py`) +* `stochastic_multithread_simulation.py` (calling `sllgs_solver.py`) is the script +that helps you launching parallel python s-LLGS simulations +* These results can be compared against Fooker-Plank simulations (see `plot_sllgs_fpe_comparison.py` script) +* `analytical.py` and `mtj_fp_fvm.py` contain the Fooker-Plank solvers. Analytical contains the WER/RER fitter for the problem optimization +* Verilog-a compact models: run the testbenches `tb.scs` and `tb_subckt.scs` + +Please, read the full description at [MRAM Framework Description](./doc/README.md). + +**IMPORTANT: Before using the compact models**, read the [s-LLGS Solvers](#s-llgs-solvers) info. + + +## Files organization +* `doc` + * [README.md](./doc/README.md) for the **full MRAM framework description** +* `src` + * `python_compact_model` + * [README.md](./python_compact_model/README.md) for the MRAM python s-LLGS description + * `sllgs_solver.py` Python s-LLGS solver + * `stochastic_multithread_simulation.py` Multi-thread stochastic simulations + * `test_sllgs_solver.py` Simple s-LLGS tests + * `ode_solver_custom_fn.py` *solve_ivp* auxilar fns derived from Scipy + * `sllgs_fpe_comparison` + * `plot_sllgs_fpe_comparison.py` Script for s-LLGS/Fooker-Plank comparison + * `sllgs_importer.py` Script for importing `stochastic_multithread_simulation.py` results + * `fokker_plank` + * [README.md](./fokker_plank/README.md) for the MRAM Fokker-Plank description + * `fvm` + * `fvm_classes.py` Finite Volume Method classes, see [FVM](https://github.com/danieljfarrell/FVM) + * `mtj_fp_fvm.py` MTJ Fokker-Plank FVM solver + * `analytical` + * `analytical.py` MTJ Fokker-Plank Analytical solver + and WER/RER curves fitter + * `verilog_a_compact_model` + * [README.md](./verilog_a_compact_model/README.md) for the MRAM verilog-a compact model description + * `tb` Testbenches + * `tb.scs` Example testbench calling full Verilog-a model (conduction and s-LLGS fully written in verilog-a) + * `tb_subckt.scs` Example testbench calling full Spectre subcircuit model (s-LLGS fully written in verilog-a, conduction writen in Spectre) + * `mram_lib` Verilog-a compact model and Spectre library + * `llg_spherical_solver.va` Verilog-a s-LLGS solver, key file + * `*.va` Parameters or auxiliar functions + * `*.scs` Spectre subcircuits and library + + +## s-LLGS Solvers + +### No-thermal or emulated-thermal simulations +* Use Scipy solver in python (`scipy_ivp=True`) +* Use Spherical coordinates +* Control the simulation through tolerances (`atol, rtol` in python) + +``` + # No thermal, no fake_thermal, solved with scipy_ivp + llg_a = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_a = llg_a.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + rtol=1e-4, + atol=1e-9) + # No thermal, EMULATED THERMAL, solved with scipy_ivp + llg_b = sllg_solver.LLG(do_fake_thermal=True, + d_theta_fake_th=1/30, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_b = llg_b.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + max_step=1e-11, + rtol=1e-4, + atol=1e-9) +``` +### Thermal Stochastic Simulations +Require stochastic differential equation solvers +* Use SDE solvers in python (`scipy_ivp=False`) +* Use Cartesian coordinates +* Control the simulation through maximum time step (`max_step` in python) +``` + llg_c = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=True, + i_amp_fn=current, + seed=seed_0) + data_c = llg_c.solve_and_plot(10e-9, + scipy_ivp=False, + solve_spherical=False, + solve_normalized=True, + max_step=1e-13, + method='stratonovich_heun') +``` +![MRAM Magnetization and stochasticity](./doc/fig4_movie.gif) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..98c6efd --- /dev/null +++ b/doc/README.md @@ -0,0 +1,502 @@ + + + +* [Authors and Related Publications](#authors-and-related-publications) +* [Motivation](#motivation) + * [Introduction to MRAM technologies](#introduction-to-mram-technologies) + * [MRAM stochasticity](#mram-stochasticity) +* [Arm's MRAM Simulation Framework](#arms-mram-simulation-framework) + * ["A Compact Model for Scalable MTJ Simulation", or how to reliably and efficiently simulate circuits with MRAM, including stochasticity](#a-compact-model-for-scalable-mtj-simulation-or-how-to-reliably-and-efficiently-simulate-circuits-with-mram-including-stochasticity) + * [Motivation for a new Verilog-a/Python Compact model](#motivation-for-a-new-verilog-apython-compact-model) + * [Coordinate Systems and Implications](#coordinate-systems-and-implications) + * [Integration Mechanism and Solver](#integration-mechanism-and-solver) + * [Thermal Noise effects and Damping](#thermal-noise-effects-and-damping) + * [Proposed Framework and Compact Model](#proposed-framework-and-compact-model) + * [1-Mb MRAM Macro Benchmark and Conclusions](#1-mb-mram-macro-benchmark-and-conclusions) + * ["A Fokker-Planck Solver to Model MTJ Stochasticity", or how to efficiently analyze stochasticity in MRAM.](#a-fokker-planck-solver-to-model-mtj-stochasticity-or-how-to-efficiently-analyze-stochasticity-in-mram) + * [Fokker-Plank Equation](#fokker-plank-equation) + * [Presented Solution Framework](#presented-solution-framework) +* [References](#references) + + + +# Authors and Related Publications + +* Fernando Garcia Redondo, +* Pranay Prabhat +* Mudit Bhargava + + +Thanks to Cyrille Dray and Milos Milosavljevic for his helpful discussions. + +The following frameworks have been presented at +* ***A Compact Model for Scalable MTJ Simulation***, IEEE International Conference on Synthesis, Modeling, Analysis and Simulation Methods and Applications to Circuit Design, SMACD 2021. +* ***A Fokker-Planck Solver to Model MTJ Stochasticity*** European Solid-State Device Research Conference, ESSDERC 2021. + + +# Motivation + +## Introduction to MRAM technologies + +Since it was discovered in 1975, Tunneling magnetoresistance (TMR) +has been actively investigated. +Since 2000s, the advances in process technologies have made possible +the miniaturization of the Magnetic-Random-Access-Memories (MRAMs) based on TMR devices, +together with its integration into traditional CMOS processes. + +![MRAM Cell](./fig1.png) +**Figure 1. MRAM Structure** + +As depicted in Figure 1, the basic MTJ structure is composed of two ferromagnetic materials insulated by a (traditionally) oxide layer. +The atom spins in each layer constitute the layer magnetization. +The pinned-layer's magnetization is fixed, but the free-layer's magnetization can be +altered. +The resistivity of the cell is determined by the magnetization direction of the two layers. +The resistance between the two terminals is minimum when both FL and PL magnetizations are parallel, (P State) and maximum when anti-parallel (AP State). + +The discovery and later industrial manufacturing of Spin-Transfer-Torque (STT) MRAMs, +coming with enough endurance, retention, scalability and lower power consumptions, +postulated MRAM as the replacement of FLASH as the +near future dominant Non-Volatile Memories (NVMs) technology. + +In STT MRAMs the writing current flowing through the device produces a torque +momentum over the FL magnetization, flipping it should the current be large enough. +Figure 2 describes how the magnetization vector evolves through time, from +a *z* ~ +1, to *z* ~ -1. + +![MRAM Magnetization during switching](./fig2_movie.gif) +**Figure 2. MRAM Magnetization during switching** + +Bottom graph describes the *x, y, z* components magnetization, +which we will use throughout this article. +The temporal evolution of the MTJ magnetization m as a monodomain +nanomagnet, influenced by external and anisotropy +fields, thermal noise and STT, is described by the stochastic Landau-Lifshitz-Gilbert- +Slonczewsky (s-LLGS) equation [OOMMF]. + +![MRAM Magnetization during switching](./fig3.png) +![MRAM Magnetization during switching](./fig3b.png) +**Figure 3. s-LLGS equations** + +Where the effective field, Heff, is determined by the external field, +the anisotropy field (uniaxial and demagnetization), the voltage controlled anisotropy field, +and the thermal field. +The STT spin term is defined by the MRAM characteristics and the current applied between the two cell terminals. + + +## MRAM stochasticity + +The random thermal noise induced field, Hth, converts the deterministic LLGS differential equation +into a stochastic differential system (s-LLGS). +Leaving process/voltage/temperature (PVT) variations out of the picture, this stochasticity +translates into uncertainty during operations. +The same cell, written at two different moments in time, behaves differently. +Consequently an MRAM cannot be considered a deterministic system, but a stochastic one. + +In the next figure, we represent the *x, y, z* magnetization components +during two different write operations, occurring **on the same cell**. +Even though no PVT variations are present, we see how in two writing iterations (0 in blue, 1 in orange), +the writing time (*z* flipping sign) varies considerably. + +![MRAM Magnetization and stochasticity](./fig4_movie.gif) +**Figure 4. Stochasticity causes two different write operations in the same cell to behave differently** + + +Translated to the circuit domain, the periphery surrounding the MRAM cell should be +aware of this behavior, and designed accordingly to be able to reliably write/read +the MRAM cells incurring into low Write/Read Error Rates (WER and RER respectively). +**Therefore the circuit design relies then on the accurate modeling and simulation of +the stochastic behavior of MRAM cells.** + +Moreover, the simulation of **stochastic differential equation (SDE) systems require +the use of Ito or Stratonovich calculus** *[P. Horley, S. Ament]*. +Standard methods are not applicable, +and the simulation of SDEs becomes a problem that involves myriad of simulations +using of small time step, requiring huge computational resources. + +In these document, related to the works presented at ***"A Compact Model for Scalable MTJ Simulation", SMACD 2021***, +and ***"A Fokker-Planck Solver to Model MTJ Stochasticity" ESSDERC 2021***, we +present the solutions to the following two problems: +* **how to reliably and efficiently simulate circuits with MRAM compact models, including stochasticity?** +* **how to efficiently analyze stochasticity?** + +In the following sections we present the solutions we developed answering these questions. + +# Arm's MRAM Simulation Framework + +We present a framework for the characterization +and analysis of MRAM stochasticity, and a compact model and framework +for the efficient and scalable simulation of circuits with MRAMs. + +We provide Verilog-A and Python compact models, able to emulate the behavior of +MRAMs switching at significant statistic events. +To calibrate the models for such stochastic based events, +we implemented and analyzed two FPE solvers (numerical FVM and analytical), and +presented an optimization module that orchestrates the efficient computation +of MRAM statistics and parameter regression. + +## "A Compact Model for Scalable MTJ Simulation", or how to reliably and efficiently simulate circuits with MRAM, including stochasticity + + +### Motivation for a new Verilog-a/Python Compact model + +Many MTJ models have been presented, from micro-magnetic approaches to macro-magnetic SPICE and +Verilog-A compact models for circuit simulations. +Compact models present in the literature account for +different behavioral aspects: a better temperature dependence, +a more accurate anisotropy formulation for particular MTJ +structures, or the integration of Spin-Orbit Torque +(SOT) for three-terminal devices, for example. + +These prior works focus on the ability to model a specific new behavior for the +simulation of a single or a few MTJ devices in a small circuit. +A product-ready PDKs requires not only research contributions capturing novel device +behavior, but also optimization stages enabling +circuit design. + +#### Coordinate Systems and Implications +STT-MRAM circuit design needs the +complex device dynamics to be incorporated into the standard +SPICE-like solvers. +And doing it efficiently is not an easy task. +The s-LLGS system resolution can easily lead to artificial +oscillations if solved in a Cartesian reference system when +using the Euler, Gear and Trapezoidal methods provided by +circuit solvers. +In *[S. Ament]* it is studied how MTJ devices benefit +from resolution in spherical coordinates, specially when using +implicit midpoint and explicit RK45 methods. Additionally, +Cartesian methods require a non-physical normalization +stage for the magnetization vector *[S. Ament]*. + +#### Integration Mechanism and Solver +Secondly, MTJ Verilog-A models present in the literature use a naive Euler/Trapezoidal +methods to integrate the magnetization vector + +*m(t) = m(t − dt) + dmdt (2)* + +However, Equation 1 causes *m_φ* to exceed the *[0, 2π)* +physically allowed range, +and grow indefinitely over time, leading +to unnatural voltages that eventually prevent simulator convergence. +Our solution is to use the circular integration idtmod +function provided by Spectre solvers, which wraps *m_φ* at +2π preventing it from increasing indefinitely. + +More importantly, there is a hard trade-off when simulating *m_θ* on Equation (1) +between its accuracy, the integration method and the minimum timestep used. +Vector *m_θ* represents the binary state of the MTJ data +bit and is the most critical component to accurately model +MTJ switching. + +![MRAM Simulation and Integration Methods](./fig5.png) +**Figure 5. Solver/time-step and accuracy trade-off.** + +The naive integration of *m_θ* directly encoding +Equation 1 in the Verilog-A description incurs a substantial +error during a switching event, as shown in Figure 5. +Reducing the timestep improves the RMS error (against OOMMF reference) but at the cost of a large +increase in computational load. Our solution is to use the idt +function for the circuit solvers provided by Spectre, +which adapt the integration based on multiple evaluations of +the differential terms at different timesteps, leading to better +accuracy. + +Regarding scalability, if a larger circuit is to be simulated and fixed timesteps +banned, it is essential to manage the tolerance/timestep scheme, +Otherwise during the computation of the *m_φ* component the asymptote +*dmφ/dt → ∞* at *m_θ = 0*. +This precession mechanism acceleration, described by the next figure, +requires the solver to accordingly increase its resolution. +Thus, an appropriate tolerance scheme is to be used. +![MRAM Simulation and Integration Methods](./fig6.png) +**Figure 6. Asymptote simulation requires smaller time steps to ensure convergence.** + +#### Thermal Noise effects and Damping +*H_th* follows a Wiener process in which each *x, y, z* +random component is independent of each other and previous +states. +This implies that the computation of *H_th* requires large +independent variations between steps, hindering the solver’s +attempts to guarantee signal continuity under small tolerances. +Under default solver tolerances aiming for circuit accuracy, the simulation +leads to computational errors, and excessive computational +load under 1 ps bounded time steps, and require from user-defined Stratonovich +Heun/RK extra steps. + +Three solutions have been proposed in the literature. First, +to emulate the random field by using an external current or +resistor-like noise source. However, SPICE simulators +impose a minimum capacitance on these nodes filtering the +randomness response, therefore preventing a true Wiener process +from being simulated, as they are later integrated using deterministic methods. +Second, to bound a fixed small +timestep to the solver, but as described before this is +not feasible for large circuits. Third, to only consider scenarios +where the field generated by the writing current is much +larger than *H_th* , forcing *H_th = 0*. + +This has strong implications when moving from single to multiple successive +switching event simulations. Under no *H_th* thermal field, +the magnetization damping collapses *m_θ* to either *0* or *π*. +s-LLGS dynamics imply that the smaller the *m_θ* the harder +it is for the cell to switch, and if completely collapsed, it is +impossible. + +![MRAM Simulation and Integration Methods](./fig7.png) +**Figure 7. *m_z* collapses *m_θ=0* due to dampening under no thermal noise field presence, +Making impossible to simulate multiple-writes or long simulations** + +The above picture depicts this artificial effect, which does +not have an equivalent in reality, as *H_th != 0* imposes a +random angle. Design, validation and signoff for large memory +blocks with integrated periphery and control circuits requires +the simulation of sequences of read and write operations, with +each operation showing identical predictable and switching +characteristics. However, the damping artifact discussed above +prevents or slows down subsequent switches after the first +event, since the subsequent events see an initial *θ* value +vanishingly close to zero. + +### Proposed Framework and Compact Model +![MRAM Simulation and Integration Methods](./fig8.png) +**Figure 8. Proposed compact model and Framework methodology.** + +The above Figure describes the implemented compact model +and model analysis/calibration procedure and validation against OOMMF. +First, given a set of MRAM parameters, initial non-stochastic simulations +are compared against OOMMF simulation results. The tolerances are adjusted +till the results match. +At this point, the model is frozen and exported to Verilog-A. +The subsequent simulation validates the tolerances needed for the required accuracy. +Finally, the coefficients of the thermal noise emulation mechanism explained bellow +are regressed and the Verilog-A model library finalized. + +The model is composed of two modules: +the Conduction and Dynamics modules. +The conduction scheme describing the instantaneous MTJ +resistance is dependent on the foundry engineered stack. Our +modular approach allows foundry-specific conduction mechanisms to complement +the basic Tunnel-Magneto-Resistance +(TMR) scheme. The Dynamics module describes +the temporal evolution of the MTJ magnetization *m*. + +The compact model has been implemented in Python and Verilog-A. +Python model supports traditional +Ordinary Differential Equations (ODE) and SDE solvers, for the +simulation of *H_th* as a pure Wiener process [S. Ament, P. Horley]. +The parallel Python engine enables MC and statistical studies. The +Verilog-A implementation uses idt /idtmod integration +schemes with parameterizable integration tolerances. +The following Figure describes our Verilog-A model once parameterized validated against OOMMF. +![MRAM Simulation and Integration Methods](./fig9.png) +**Figure 9. Validation against OOMMF** + +To enable the efficient simulation of the effects caused by the stochastic *H_th* +two solutions are proposed. +The purpose is to be able to simulate +being simulate the mean switching behavior, and those switchings related +to given error rate *WER_i* behaviors. Thus, enabling the analysis of +how a given circuit instantiating that MRAM device would behave statistically +with negligible simulation performance degradation. + +**Windowing Function Approach:** Our first objective is +to provide a mechanism for *m_θ* to saturate naturally to the +equilibrium value given by *H_th* during switching events. By +redefining the evolution of *m* on its *θ* component we are able +to saturate its angle at *θ_0* , the second moment of the Maxwell- +Boltzmann distribution of *m_θ* under no external field [OOMMF] + +This function slows down the magnetization evolution +when reaching the angle *c_wi θ_0*, therefore saturating *m_θ* +and avoiding *m* from collapsing over *z*. Moreover, by using +*c_wi* we are able to define the angle that statistically follows +different stochastic behaviors corresponding to different write error rates. + +**Emulated Magnetic Term Approach** +The expansion of s-LLGS equation after its expression in spherical coordinates +describes *m_θ* evolution as proportional to *H_eff_φ + αH_eff_θ ~ H_eff_φ*. +We propose to add a fictitious term *H_fth_φ* with the purpose of +emulating the mean/*WER_i* *H_th* contribution that generates *θ_0* (*θ_0_i* for *WER_i*). + +Thanks to the proposed approaches we can efficiently extract the mean/*WER_i* behaviors, +and generate the corresponding and calibrated Verilog-A models ready to be efficiently simulated. +![MRAM Simulation and Integration Methods](./fig10.png) +**Figure 10. Stochastic SDE simulations requiring high computational resources and proposed *H_fth* simulation, +matching the mean stochastic behavior of the cell** + +### 1-Mb MRAM Macro Benchmark and Conclusions +To validate scalability on a commercial product, the model +is instantiated into the 64 × 4 memory top block of the +extracted netlist from a 1-Mb 28 nm MRAM macro [E. M. Boujamma], and +simulated with macro-specific tolerance settings. The emulated +magnetic term enables the previously +impossible capability of simulating successive writes with +identical transition times due to non-desired over-damping. + +The following Figure –resistance/voltage units omitted for confidentiality– +describes a writing operation 10 µs after power-on sequence. +We combine the s-LLGS OOMMF validated dynamics with +foundry-given thermal/voltage conductance dependence, providing +the accurate resistance response over time. Compared +to using fixed resistors, there is an overhead of 3.1× CPU time +and 1.5× RAM usage. In return, circuit designers can observe +accurate transient switching behaviour and read disturbs. +![MRAM Simulation and Integration Methods](./fig11.png) +**Figure 11. Magnetization, BL, WL, SL and resistance of a cell writen within a 1Mb Macro** + + +## "A Fokker-Planck Solver to Model MTJ Stochasticity", or how to efficiently analyze stochasticity in MRAM. + +As seen in Figure 10, the computation of +WER/RER with s-LLGS simulations requires a large number +of random walks, especially for the low error rates (<< 1ppm) +required for volume production. + +For example, the simulation of 10000 random walks solving the SDE using Stratonovich +Heun algorithm in Cartesian coordinates system (0.1ps time step) +for the write error rate computation seen in Figure 12 took ~1150 hours. +The characterization of a given MRAM cell for *WER=1e-8* +would require a non-manageable amount of time and computational resources. +![MRAM Simulation and Integration Methods](./fig12.png) +**Figure 12. WER simulation. 10000 SDE simulations using Stratonovich Heun method, +solved for cartesian coordinates system and 0.1ps time step took ~1150 hours. +Taking advantage of the presented python framework and multi-thread capabilities, +this computation time was reduced to ~18h (64 threads). +The Fokker-Plank WER computation is solved in seconds, providing the analytical solution.** + +To alleviate this issue, Stochastic Differential Equation +(SDE) tools such as the Fokker–Planck Equation (FPE) statis- +tically analyze the MTJ magnetization and provide a simplified +solution with sufficient accuracy to analyze such error rates +[Tzoufras], [Y. Xie], [W. H. Butler]. +Compared against a set of s-LLGS random-walk +transient simulations, the FPE accurately evolves an initial +MTJ magnetization probability through time based on the cur- +rent and external fields. Instead of independent transients, the +FPE computes the probability distribution of the magnetization +at a given instant, thus capturing the statistical behavior of the +MTJ cell. Figure 12 highlights how FPE is able to solve in seconds +what otherwise requires huge computational resources (myriads of s-LLGS simulations). + +The problem magnifies when fitting to measured silicon data. Silicon measurement needs +low error rates to be captured accurately from finite memory +arrays without compromising test throughput in volume production, +requiring high currents to allow extrapolation from +higher, more easily measurable error rates. +As a result, foundry +data could consist of a set of data points with the error rate +spanning orders of magnitude. + +Addressing both problems, +the proposed stochastic framework +for the characterization and analysis of MRAM stochastic effects is described, +and the fitting of the complex set of MRAM parameters onto +such heterogeneous data points through a case study with +published foundry data. + +With the proposed solution, we are able to generate WER/RER in seconds, +enabling the search of the set of physical parameters +that best fit a collection of ER points as a +function of a current pulse width and amplitude. + +### Fokker-Plank Equation +The advection-diffusion or Fokker-Plank equation has been widely used to analyze +the evolution over time of the +probability density function of the magnetization of an MRAM cell: +![MRAM Simulation and Integration Methods](./fig13.png) +**Figure 13. Fokker-Plank equation** + +Prior work numerically solves the FPE through finite differences +or finite volume methods (FVM) or analytical solutions. +![MRAM Simulation and Integration Methods](./fig15_.png) +**Figure 14. Computational load comparison of a single s-LLGS random walk and different FPE solvers** +In the above Figure we characterize the computational load required to simulate +a single s-LLGS stochastic random walk, different FVM FPE simulations using +different time and spatial resolutions, and various analytical FPE simulations +varying the number of coefficients of the expansion series used. +First, it can be clearly seen how computing millions of s-LLGS is simply unmanageable. +Second, while FVM FPE approaches are a good method for small amount of WER/RER computations, +should multiple simulations be required, like it is the case on the design space exploration +occurring during MRAM parameters fitting, the faster yet accurate analytical +solutions approach is required. + +It is important to highlight that even FVM FPE solutions constituted a reasonable +solution enabling statistical analyses otherwise impossible using billions of s-LLGS, +the simulation time is directly proportional at the writing pulse width being simulated. +Therefore longer simulated times, required to characterize low-current regimes, +still involve huge computational resources. +On the contrary, the analytical FPE approach otherwise requires constant time to simulate +longer pulse widths. + +The following Figure describes how the different resolutions and number of coefficients used +affect the accuracy of the computation of the magnetization probability evolution. +![MRAM Simulation and Integration Methods](./fig15b.png) +**Figure 15. Accuracy comparision for different resolution/number of coefficients.** + +### Presented Solution Framework + +Complementing the compact model and simulation framework that enable the +efficient simulation of circuits with MRAMs, the MRAM characterization +framework focus on the MRAM behavior statistical analysis. + +As described in the following Figure, +it enables the otherwise impossible MRAM parameter set regression from +foundry WER curves. +![MRAM Simulation and Integration Methods](./fig16.png) +**Figure 16. Proposed framework and methodology** + +At the end of the process, the circuit designer obtains a set of MRAM compact models, +ready to be simulated in traditional circuit simulators, that have been +accurately calibrated to represent the mean/*WER_i* cell behavior. + +As a case of study, we take the two stack processes presented in [G. Hu], +where a set of current/mean switching time points are provided. +First the most computing intensive tasks, regressing the MRAM parameters set +that best describe the provided stochastic behaviors takes place. +Our framework finds the proper parameters in reasonable time (*< 3 days*), +a task otherwise completely impossible using s-LLGS simulations, +and much slower (almost impractical) using FVM approaches. +![MRAM Simulation and Integration Methods](./fig17.png) +**Figure 17. Current/switching time fitting for two MRAM stack processes without any prior knowledge** + +At this point, as circuit designers we can easily compare the error rates +on both stacks, and determine the appropriate operating points, based on results +as the shown in the following Figure: +![MRAM Simulation and Integration Methods](./fig18.png) +**Figure 18. WER/RER analyses for the regressed stacks** + +With the physical parameters set found, we make use of the compact model presented at +["A Compact Model for Scalable MTJ Simulation"](#a-compact-model-for-scalable-mtj-simulation-or-how-to-reliably-and-efficiently-simulate-circuits-with-mram-including-stochasticity) +section, and compute the *H_fth_i* parameters that enable circuit designers +to accurately simulate the required MRAM cell at the significant *WER_i* statistical events: +![MRAM Simulation and Integration Methods](./fig19.png) + + +# References + +[P. Horley] Horley, P., et al. (2011). +Numerical Simulations of Nano-Scale Magnetization Dynamics. +Numerical Simulations of Physical and Engineering Processes. https://doi.org/10.5772/23745 + +[S. Ament] S. Ament et al., “Solving the stochastic Landau-Lifshitz-Gilbert- +Slonczewski equation for monodomain nanomagnets : A survey and +analysis of numerical techniques,” 2016. + +[OOMMF] Donahue, M. J., & Porter, D. G. (1999). +OOMMF user’s guide, version 1.0. In National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.6376 + +[E. M. Boujamaa] E. M. Boujamaa et al., “A 14.7Mb/mm2 28nm FDSOI STT-MRAM with +Current Starved Read Path, 52Ω/Sigma Offset Voltage Sense Amplifier +and Fully Trimmable CTAT Reference,” IEEE Symp. VLSI Circuits, Dig. +Tech. Pap., vol. 2020-June, 2020. + +[Tzoufras] Tzoufras, M. (2018). Switching probability of all-perpendicular spin valve nanopillars. AIP Advances, 8(5). https://doi.org/10.1063/1.5003832 + +[Y.Xie] Xie, Y., et al. (2017). Fokker-Planck Study of Parameter Dependence on Write Error Slope in Spin-Torque Switching. IEEE Transactions on Electron Devices, 64(1), 319–324. https://doi.org/10.1109/TED.2016.2632438 + +[W. H. Butler] Butler, W. H., Mewes, T., Mewes, C. K. A., Visscher, P. B., Rippard, W. H., Russek, S. E., & Heindl, R. (2012). Switching distributions for perpendicular spin-torque devices within the macrospin approximation. IEEE Transactions on Magnetics, 48(12), 4684–4700. https://doi.org/10.1109/TMAG.2012.2209122 + +[G. Hu] Hu G. et al., “Spin-transfer torque MRAM with reliable 2 ns writing for +last level cache applications,” Tech. Dig. - Int. Electron Devices Meet. +IEDM, vol. 2019-Decem, pp. 2019–2022, 2019. diff --git a/doc/blog_1.md b/doc/blog_1.md new file mode 100644 index 0000000..f4644f9 --- /dev/null +++ b/doc/blog_1.md @@ -0,0 +1,304 @@ + + + +* [Motivation](#motivation) + * [Introduction to MRAM technologies](#introduction-to-mram-technologies) + * [MRAM stochasticity](#mram-stochasticity) + * ["A Compact Model for Scalable MTJ Simulation", or how to reliably and efficiently simulate circuits with MRAM, including stochasticity](#a-compact-model-for-scalable-mtj-simulation-or-how-to-reliably-and-efficiently-simulate-circuits-with-mram-including-stochasticity) + * [Motivation for a new Verilog-a/Python Compact model](#motivation-for-a-new-verilog-apython-compact-model) + * [Proposed Framework and Compact Model](#proposed-framework-and-compact-model) + * [1-Mb MRAM Macro Benchmark and Conclusions](#1-mb-mram-macro-benchmark-and-conclusions) + * [But the MRAM story continues](#but-the-mram-story-continues) +* [References](#references) + + + +* Fernando Garcia Redondo, +* Pranay Prabhat +* Mudit Bhargava + + +Thanks to Cyrille Dray and Milos Milosavljevic for his helpful discussions. + +The following frameworks have been presented at +* ***A Compact Model for Scalable MTJ Simulation***, IEEE International Conference on Synthesis, Modeling, Analysis and Simulation Methods and Applications to Circuit Design, SMACD 2021. +* ***A Fokker-Planck Solver to Model MTJ Stochasticity*** European Solid-State Device Research Conference, ESSDERC 2021. + + +# Motivation + +## Introduction to MRAM technologies + +Since it was discovered in 1975, Tunneling magnetoresistance (TMR) +has been actively investigated. +Since 2000s, the advances in process technologies have made possible +the miniaturization of the Magnetic-Random-Access-Memories (MRAMs) based on TMR devices, +together with its integration into traditional CMOS processes. + +![MRAM Cell](./fig1.png) +**Figure 1. MRAM Structure** + +As depicted in Figure 1, the basic MTJ structure is composed of two ferromagnetic materials insulated by a (traditionally) oxide layer. +The atom spins in each layer constitute the layer magnetization. +The pinned-layer's magnetization is fixed, but the free-layer's magnetization can be +altered. +The resistivity of the cell is determined by the magnetization direction of the two layers. +The resistance between the two terminals is minimum when both FL and PL magnetizations are parallel, (P State) and maximum when anti-parallel (AP State). + +The discovery and later industrial manufacturing of Spin-Transfer-Torque (STT) MRAMs, +coming with enough endurance, retention, scalability and lower power consumptions, +postulated MRAM as the replacement of FLASH as the +near future dominant Non-Volatile Memories (NVMs) technology. + +In STT MRAMs the writing current flowing through the device produces a torque +momentum over the FL magnetization, flipping it should the current be large enough. +Figure 2 describes how the magnetization vector evolves through time, from +a *z* ~ +1, to *z* ~ -1. + +![MRAM Magnetization during switching](./fig2_movie.gif) +**Figure 2. MRAM Magnetization during switching** + +Bottom graph describes the *x, y, z* components magnetization, +which we will use throughout this article. +The temporal evolution of the MTJ magnetization m as a monodomain +nanomagnet, influenced by external and anisotropy +fields, thermal noise and STT, is described by the stochastic Landau-Lifshitz-Gilbert- +Slonczewsky (s-LLGS) equation [OOMMF]. + +![MRAM Magnetization during switching](./fig3.png) +![MRAM Magnetization during switching](./fig3b.png) +**Figure 3. s-LLGS equations** + +Where the effective field, Heff, is determined by the external field, +the anisotropy field (uniaxial and demagnetization), the voltage controlled anisotropy field, +and the thermal field. +The STT spin term is defined by the MRAM characteristics and the current applied between the two cell terminals. + + +## MRAM stochasticity + +The random thermal noise induced field, Hth, converts the deterministic LLGS differential equation +into a stochastic differential system (s-LLGS). +Leaving process/voltage/temperature (PVT) variations out of the picture, this stochasticity +translates into uncertainty during operations. +The same cell, written at two different moments in time, behaves differently. +Consequently an MRAM cannot be considered a deterministic system, but a stochastic one. + +In the next figure, we represent the *x, y, z* magnetization components +during two different write operations, occurring **on the same cell**. +Even though no PVT variations are present, we see how in two writing iterations (0 in blue, 1 in orange), +the writing time (*z* flipping sign) varies considerably. + +![MRAM Magnetization and stochasticity](./fig4_movie.gif) +**Figure 4. Stochasticity causes two different write operations in the same cell to behave differently** + + +Translated to the circuit domain, the periphery surrounding the MRAM cell should be +aware of this behavior, and designed accordingly to be able to reliably write/read +the MRAM cells incurring into low Write/Read Error Rates (WER and RER respectively). +**Therefore the circuit design relies then on the accurate modeling and simulation of +the stochastic behavior of MRAM cells.** + +Moreover, the simulation of **stochastic differential equation (SDE) systems require +the use of Ito or Stratonovich calculus** *[P. Horley, S. Ament]*. +Standard methods are not applicable, +and the simulation of SDEs becomes a problem that involves myriad of simulations +using of small time step, requiring huge computational resources. + +In these blog posts related to the works presented at ***"A Compact Model for Scalable MTJ Simulation", SMACD 2021***, +and ***"A Fokker-Planck Solver to Model MTJ Stochasticity" ESSDERC 2021***, +we present a framework for the characterization +and analysis of MRAM stochasticity, and a compact model and framework +for the efficient and scalable simulation of circuits with MRAMs. + +We provide Verilog-A and Python compact models, able to emulate the behavior of +MRAMs switching at significant statistic events. +To calibrate the models for such stochastic based events, +we implemented and analyzed two FPE solvers (numerical FVM and analytical), and +presented an optimization module that orchestrates the efficient computation +of MRAM statistics and parameter regression. + +Therefore, in this series of posts, we present the solutions and answers +to the following two problems: +* **how to reliably and efficiently simulate circuits with MRAM compact models, including stochasticity?**, blog post #1 +* **how to efficiently analyze stochasticity?**, blog post #2 + + +## "A Compact Model for Scalable MTJ Simulation", or how to reliably and efficiently simulate circuits with MRAM, including stochasticity + + +### Motivation for a new Verilog-a/Python Compact model + +Many MTJ models have been presented, from micro-magnetic approaches to macro-magnetic SPICE and +Verilog-A compact models for circuit simulations. +Compact models present in the literature account for +different behavioral aspects: a better temperature dependence, +a more accurate anisotropy formulation for particular MTJ +structures, or the integration of Spin-Orbit Torque +(SOT) for three-terminal devices, for example. + +These prior works focus on the ability to model a specific new behavior for the +simulation of a single or a few MTJ devices in a small circuit. +A product-ready PDKs requires not only research contributions capturing novel device +behavior, but also optimization stages enabling +circuit design. + +STT-MRAM circuit design needs the +complex device dynamics to be incorporated into the standard +SPICE-like solvers. +And doing it efficiently is not an easy task. +The s-LLGS system resolution, even when the stochasticity is not under consideration, +can easily lead to non-convergence and error issues. + +Following the work initiated in *[S. Ament]* +we analyze the integration methods and solver problems most commonly encountered +when solving s-LLGS systems, focusing on the SPICE-like circuit simulators approach. +We study the accuracy, time-step and solver characteristics trade-offs. +In particular, and focusing on the issues related to explicit integration methods, +we analyze the time-step dependence or the problematic of simulating +a Wiener process using Euler/Trapezoidal integrators. + +Similarly important is the computational resources involved in simulating +*H_th* stochastic simulations using the appropriate Stratonovich stochastic differential +calculus. +This implies that the computation of *H_th* requires large +independent variations between steps, hindering the solver’s +attempts to guarantee signal continuity under small tolerances. +Under default solver tolerances aiming for circuit accuracy, the simulation +leads to computational errors, and excessive computational +load under 1 ps bounded time steps, and require from user-defined Stratonovich +Heun/RK extra steps. + +Solutions have been proposed in the literature. First, +to emulate the random field by using an external current or +resistor-like noise source. However, this noise sources +are not appropriate for simulating true Wiener processes, +as they are later integrated using deterministic methods. +The second solutions only consider scenarios +where the field generated by the writing current is much +larger than *H_th* , forcing *H_th = 0*. +This has strong implications when moving from single to multiple successive +switching event simulations. Under no *H_th* thermal field, +the magnetization damping collapses *m_θ* to either *0* or *π*. +s-LLGS dynamics imply that the smaller the *m_θ* the harder +it is for the cell to switch, and if completely collapsed, it is +impossible. + +![MRAM Simulation and Integration Methods](./fig7.png) +**Figure 5. *m_z* collapses *m_θ=0* due to dampening under no thermal noise field presence, +Making impossible to simulate multiple-writes or long simulations** + +The above picture depicts this artificial effect, which does +not have an equivalent in reality, as *H_th != 0* imposes a +random angle. Design, validation and signoff for large memory +blocks with integrated periphery and control circuits requires +the simulation of sequences of read and write operations, with +each operation showing identical predictable and switching +characteristics. However, the damping artifact discussed above +prevents or slows down subsequent switches after the first +event, since the subsequent events see an initial *θ* value +vanishingly close to zero. + +### Proposed Framework and Compact Model +![MRAM Simulation and Integration Methods](./fig8.png) +**Figure 8. Proposed compact model and Framework methodology.** + +The above Figure describes the implemented compact model +and model analysis/calibration procedure and validation against OOMMF. +First, given a set of MRAM parameters, initial non-stochastic simulations +are compared against OOMMF simulation results. The tolerances are adjusted +till the results match. +At this point, the model is frozen and exported to Verilog-A. +The subsequent simulation validates the tolerances needed for the required accuracy. +Finally, the coefficients of the thermal noise emulation mechanism explained bellow +are regressed and the Verilog-A model library finalized. + +The model is composed of two modules: +the Conduction and Dynamics modules. +The conduction scheme describing the instantaneous MTJ +resistance is dependent on the foundry engineered stack. Our +modular approach allows foundry-specific conduction mechanisms to complement +the basic Tunnel-Magneto-Resistance +(TMR) scheme. The Dynamics module describes +the temporal evolution of the MTJ magnetization *m*. + +The compact model has been implemented in Python and Verilog-A. +Python model supports traditional +Ordinary Differential Equations (ODE) and SDE solvers, for the +simulation of *H_th* as a pure Wiener process [S. Ament, P. Horley]. +The parallel Python engine enables MC and statistical studies. The +Verilog-A implementation uses idt /idtmod integration +schemes with parameterizable integration tolerances. +The following Figure describes our Verilog-A model once parameterized validated against OOMMF. +![MRAM Simulation and Integration Methods](./fig9.png) +**Figure 9. Validation against OOMMF** + +To enable the efficient simulation of the effects caused by the stochastic *H_th* +two solutions are proposed. +The purpose is to be able to simulate +being simulate the mean switching behavior, and those switchings related +to given error rate *WER_i* behaviors. Thus, enabling the analysis of +how a given circuit instantiating that MRAM device would behave statistically +with negligible simulation performance degradation. +The first solution smoothly saturates *m_θ* preventing it to saturate completely. + +The second approach adds a fictitious term *H_fth* with the purpose of +emulating the mean/*WER_i* *H_th* contribution that generates *θ_0* (*θ_0_i* for *WER_i*). +Thanks to the proposed approaches we can efficiently extract the mean/*WER_i* behaviors, +and generate the corresponding and calibrated Verilog-A models ready to be efficiently simulated. +![MRAM Simulation and Integration Methods](./fig10.png) +**Figure 10. Stochastic SDE simulations requiring high computational resources and proposed *H_fth* simulation, +matching the mean stochastic behavior of the cell** + +### 1-Mb MRAM Macro Benchmark and Conclusions +To validate scalability on a commercial product, the model +is instantiated into the 64 × 4 memory top block of the +extracted netlist from a 1-Mb 28 nm MRAM macro [E. M. Boujamma], and +simulated with macro-specific tolerance settings. The emulated +magnetic term enables the previously +impossible capability of simulating successive writes with +identical transition times due to non-desired over-damping. + +The following Figure –resistance/voltage units omitted for confidentiality– +describes a writing operation 10 µs after power-on sequence. +We combine the s-LLGS OOMMF validated dynamics with +foundry-given thermal/voltage conductance dependence, providing +the accurate resistance response over time. Compared +to using fixed resistors, there is an overhead of 3.1× CPU time +and 1.5× RAM usage. In return, circuit designers can observe +accurate transient switching behaviour and read disturbs. +![MRAM Simulation and Integration Methods](./fig11.png) +**Figure 11. Magnetization, BL, WL, SL and resistance of a cell writen within a 1Mb Macro** + +### But the MRAM story continues + +So far, we have walked together through the adventure of designing an efficient +compact model that enables circuit designers to design scalable circuits +and simulate them on well known and required significant statistical events. + +However, the statistical analysis of an MRAM technology is unfeasible just using +s-LLGS based systems. The statistical characterization would require of millions +of stochastic LLGS walks, involving huge computational resources that would make +the validation of large circuit simply impossible. + +In the blog post ***"A Fokker-Planck Solver to Model MTJ Stochasticity", or how to efficiently analyze stochasticity in MRAM.***, we answer this question, and present a solution +for this important problem. + + +# References + +[P. Horley] Horley, P., et al. (2011). +Numerical Simulations of Nano-Scale Magnetization Dynamics. +Numerical Simulations of Physical and Engineering Processes. https://doi.org/10.5772/23745 + +[S. Ament] S. Ament et al., “Solving the stochastic Landau-Lifshitz-Gilbert- +Slonczewski equation for monodomain nanomagnets : A survey and +analysis of numerical techniques,” 2016. + +[OOMMF] Donahue, M. J., & Porter, D. G. (1999). +OOMMF user’s guide, version 1.0. In National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.6376 + +[E. M. Boujamaa] E. M. Boujamaa et al., “A 14.7Mb/mm2 28nm FDSOI STT-MRAM with +Current Starved Read Path, 52Ω/Sigma Offset Voltage Sense Amplifier +and Fully Trimmable CTAT Reference,” IEEE Symp. VLSI Circuits, Dig. +Tech. Pap., vol. 2020-June, 2020. diff --git a/doc/blog_2.md b/doc/blog_2.md new file mode 100644 index 0000000..3b17a3c --- /dev/null +++ b/doc/blog_2.md @@ -0,0 +1,211 @@ + + + +* [MRAM Stochasticity, but how do MRAM cells work?](#mram-stochasticity-but-how-do-mram-cells-work) +* ["A Fokker-Planck Solver to Model MTJ Stochasticity", or how to efficiently analyze stochasticity in MRAM.](#a-fokker-planck-solver-to-model-mtj-stochasticity-or-how-to-efficiently-analyze-stochasticity-in-mram) + * [Fokker-Plank Equation](#fokker-plank-equation) + * [Presented Solution Framework](#presented-solution-framework) +* [Arm's MRAM Simulation Framework, an overview.](#arms-mram-simulation-framework-an-overview) +* [References](#references) + + + +* Fernando Garcia Redondo, +* Pranay Prabhat +* Mudit Bhargava + + +Thanks to Cyrille Dray and Milos Milosavljevic for his helpful discussions. + +The following frameworks have been presented at +* ***A Compact Model for Scalable MTJ Simulation***, IEEE International Conference on Synthesis, Modeling, Analysis and Simulation Methods and Applications to Circuit Design, SMACD 2021. +* ***A Fokker-Planck Solver to Model MTJ Stochasticity*** European Solid-State Device Research Conference, ESSDERC 2021. + + +# MRAM Stochasticity, but how do MRAM cells work? + +In the previous post, ***"A Compact Model for Scalable MTJ Simulation", or how to reliably and efficiently simulate circuits with MRAM, including stochasticity*** we introduce +MRAM technologies, and analyze compact model for the efficient simulation +of MRAM cells in large-scale circuits. + +MRAM is a NVM technology intrinsically stochastic. +Translated to the circuit domain, the periphery surrounding the MRAM cell should be +aware of this behavior, and designed accordingly to be able to reliably write/read +the MRAM cells incurring into low Write/Read Error Rates (WER and RER respectively). +**Therefore the circuit design relies then on the accurate modeling and simulation of +the stochastic behavior of MRAM cells.** +![MRAM Magnetization and stochasticity](./fig4_movie.gif) +**Figure 1. Stochasticity causes two different write operations in the same cell to behave differently** + +Moreover, there we described how the simulation of +**stochastic differential equation (SDE) systems require +the use of Ito or Stratonovich calculus** *[P. Horley, S. Ament]*. +Standard methods are not applicable, +and the simulation of SDEs becomes a problem that involves myriad of simulations +using of small time step, requiring huge computational resources. + +In the present blog post, +related to the work ***"A Fokker-Planck Solver to Model MTJ Stochasticity" ESSDERC 2021***, +we present a framework for the characterization +and analysis of MRAM stochasticity. +To calibrate the models for such stochastic based events, +we implemented and analyzed two FPE solvers (numerical FVM and analytical), and +presented an optimization module that orchestrates the efficient computation +of MRAM statistics and parameter regression. + + +# "A Fokker-Planck Solver to Model MTJ Stochasticity", or how to efficiently analyze stochasticity in MRAM. + +As seen in Figure 10, the computation of +WER/RER with s-LLGS simulations requires a large number +of random walks, especially for the low error rates (<< 1ppm) +required for volume production. + +For example, the simulation of 10000 random walks solving the SDE using Stratonovich +Heun algorithm in Cartesian coordinates system (0.1ps time step) +for the write error rate computation seen in Figure 12 took ~1150 hours. +The characterization of a given MRAM cell for *WER=1e-8* +would require a non-manageable amount of time and computational resources. +![MRAM Simulation and Integration Methods](./fig12.png) +**Figure 12. WER simulation. 10000 SDE simulations using Stratonovich Heun method, +solved for cartesian coordinates system and 0.1ps time step took ~1150 hours. +Taking advantage of the presented python framework and multi-thread capabilities, +this computation time was reduced to ~18h (64 threads). +The Fokker-Plank WER computation is solved in seconds, providing the analytical solution.** + +To alleviate this issue, Stochastic Differential Equation +(SDE) tools such as the Fokker–Planck Equation (FPE) statis- +tically analyze the MTJ magnetization and provide a simplified +solution with sufficient accuracy to analyze such error rates +[Tzoufras], [Y. Xie], [W. H. Butler]. +Compared against a set of s-LLGS random-walk +transient simulations, the FPE accurately evolves an initial +MTJ magnetization probability through time based on the cur- +rent and external fields. Instead of independent transients, the +FPE computes the probability distribution of the magnetization +at a given instant, thus capturing the statistical behavior of the +MTJ cell. Figure 12 highlights how FPE is able to solve in seconds +what otherwise requires huge computational resources (myriads of s-LLGS simulations). + +The problem magnifies when fitting to measured silicon data. Silicon measurement needs +low error rates to be captured accurately from finite memory +arrays without compromising test throughput in volume production, +requiring high currents to allow extrapolation from +higher, more easily measurable error rates. +As a result, foundry +data could consist of a set of data points with the error rate +spanning orders of magnitude. + +Addressing both problems, +the proposed stochastic framework +for the characterization and analysis of MRAM stochastic effects is described, +and the fitting of the complex set of MRAM parameters onto +such heterogeneous data points through a case study with +published foundry data. + +With the proposed solution, we are able to generate WER/RER in seconds, +enabling the search of the set of physical parameters +that best fit a collection of ER points as a +function of a current pulse width and amplitude. + +### Fokker-Plank Equation +The advection-diffusion or Fokker-Plank equation has been widely used to analyze +the evolution over time of the +probability density function of the magnetization of an MRAM cell: +![MRAM Simulation and Integration Methods](./fig13.png) +**Figure 13. Fokker-Plank equation** + +Prior work numerically solves the FPE through finite differences +or finite volume methods (FVM) or analytical solutions. +![MRAM Simulation and Integration Methods](./fig15_.png) +**Figure 14. Computational load comparison of a single s-LLGS random walk and different FPE solvers** +In the above Figure we characterize the computational load required to simulate +a single s-LLGS stochastic random walk, different FVM FPE simulations using +different time and spatial resolutions, and various analytical FPE simulations +varying the number of coefficients of the expansion series used. +First, it can be clearly seen how computing millions of s-LLGS is simply unmanageable. +Second, while FVM FPE approaches are a good method for small amount of WER/RER computations, +should multiple simulations be required, like it is the case on the design space exploration +occurring during MRAM parameters fitting, the faster yet accurate analytical +solutions approach is required. + +It is important to highlight that even FVM FPE solutions constituted a reasonable +solution enabling statistical analyses otherwise impossible using billions of s-LLGS, +the simulation time is directly proportional at the writing pulse width being simulated. +Therefore longer simulated times, required to characterize low-current regimes, +still involve huge computational resources. +On the contrary, the analytical FPE approach otherwise requires constant time to simulate +longer pulse widths. + +### Presented Solution Framework + +Complementing the compact model and simulation framework that enable the +efficient simulation of circuits with MRAMs, the MRAM characterization +framework focus on the MRAM behavior statistical analysis. + +As described in the following Figure, +it enables the otherwise impossible MRAM parameter set regression from +foundry WER curves. +![MRAM Simulation and Integration Methods](./fig16.png) +**Figure 16. Proposed framework and methodology** + +At the end of the process, the circuit designer obtains a set of MRAM compact models, +ready to be simulated in traditional circuit simulators, that have been +accurately calibrated to represent the mean/*WER_i* cell behavior. + +As a case of study, we take the two stack processes presented in [G. Hu], +where a set of current/mean switching time points are provided. +First the most computing intensive tasks, regressing the MRAM parameters set +that best describe the provided stochastic behaviors takes place. +Our framework finds the proper parameters in reasonable time (*< 3 days*), +a task otherwise completely impossible using s-LLGS simulations, +and much slower (almost impractical) using FVM approaches. +![MRAM Simulation and Integration Methods](./fig17.png) +**Figure 17. Current/switching time fitting for two MRAM stack processes without any prior knowledge** + +With the physical parameters set found, we make use of the compact model presented at +["A Compact Model for Scalable MTJ Simulation"](#a-compact-model-for-scalable-mtj-simulation-or-how-to-reliably-and-efficiently-simulate-circuits-with-mram-including-stochasticity) +section, and compute the *H_fth_i* parameters that enable circuit designers +to accurately simulate the required MRAM cell at the significant *WER_i* statistical events: +![MRAM Simulation and Integration Methods](./fig19.png) + +# Arm's MRAM Simulation Framework, an overview. + +In this series of posts, we presented a framework for the characterization +and analysis of MRAM stochasticity, and a compact model and framework +for the efficient and scalable simulation of circuits with MRAMs. + +We provided Verilog-A and Python compact models, able to emulate the behavior of +MRAMs switching at significant statistic events. +To calibrate the models for such stochastic based events, +we implemented and analyzed two FPE solvers (numerical FVM and analytical), and +presented an optimization module that orchestrates the efficient computation +of MRAM statistics and parameter regression. + +# References + +[P. Horley] Horley, P., et al. (2011). +Numerical Simulations of Nano-Scale Magnetization Dynamics. +Numerical Simulations of Physical and Engineering Processes. https://doi.org/10.5772/23745 + +[S. Ament] S. Ament et al., “Solving the stochastic Landau-Lifshitz-Gilbert- +Slonczewski equation for monodomain nanomagnets : A survey and +analysis of numerical techniques,” 2016. + +[OOMMF] Donahue, M. J., & Porter, D. G. (1999). +OOMMF user’s guide, version 1.0. In National Institute of Standards and Technology. https://doi.org/10.6028/NIST.IR.6376 + +[E. M. Boujamaa] E. M. Boujamaa et al., “A 14.7Mb/mm2 28nm FDSOI STT-MRAM with +Current Starved Read Path, 52Ω/Sigma Offset Voltage Sense Amplifier +and Fully Trimmable CTAT Reference,” IEEE Symp. VLSI Circuits, Dig. +Tech. Pap., vol. 2020-June, 2020. + +[Tzoufras] Tzoufras, M. (2018). Switching probability of all-perpendicular spin valve nanopillars. AIP Advances, 8(5). https://doi.org/10.1063/1.5003832 + +[Y.Xie] Xie, Y., et al. (2017). Fokker-Planck Study of Parameter Dependence on Write Error Slope in Spin-Torque Switching. IEEE Transactions on Electron Devices, 64(1), 319–324. https://doi.org/10.1109/TED.2016.2632438 + +[W. H. Butler] Butler, W. H., Mewes, T., Mewes, C. K. A., Visscher, P. B., Rippard, W. H., Russek, S. E., & Heindl, R. (2012). Switching distributions for perpendicular spin-torque devices within the macrospin approximation. IEEE Transactions on Magnetics, 48(12), 4684–4700. https://doi.org/10.1109/TMAG.2012.2209122 + +[G. Hu] Hu G. et al., “Spin-transfer torque MRAM with reliable 2 ns writing for +last level cache applications,” Tech. Dig. - Int. Electron Devices Meet. +IEDM, vol. 2019-Decem, pp. 2019–2022, 2019. diff --git a/doc/blog_image_1.svg b/doc/blog_image_1.svg new file mode 100644 index 0000000..4285f06 --- /dev/null +++ b/doc/blog_image_1.svg @@ -0,0 +1,1315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pinned Layer + Free Layer + '0' + '1' + Oxide + + diff --git a/doc/fig1.png b/doc/fig1.png new file mode 100644 index 0000000..a9ddbe4 Binary files /dev/null and b/doc/fig1.png differ diff --git a/doc/fig10.png b/doc/fig10.png new file mode 100644 index 0000000..05fc7e7 Binary files /dev/null and b/doc/fig10.png differ diff --git a/doc/fig11.png b/doc/fig11.png new file mode 100644 index 0000000..d985034 Binary files /dev/null and b/doc/fig11.png differ diff --git a/doc/fig12.png b/doc/fig12.png new file mode 100644 index 0000000..a06c9da Binary files /dev/null and b/doc/fig12.png differ diff --git a/doc/fig13.png b/doc/fig13.png new file mode 100644 index 0000000..3d994df Binary files /dev/null and b/doc/fig13.png differ diff --git a/doc/fig13__.png b/doc/fig13__.png new file mode 100644 index 0000000..bb642de Binary files /dev/null and b/doc/fig13__.png differ diff --git a/doc/fig14.png b/doc/fig14.png new file mode 100644 index 0000000..5074176 Binary files /dev/null and b/doc/fig14.png differ diff --git a/doc/fig15.png b/doc/fig15.png new file mode 100644 index 0000000..eb1dfec Binary files /dev/null and b/doc/fig15.png differ diff --git a/doc/fig15_.png b/doc/fig15_.png new file mode 100644 index 0000000..eaa23d0 Binary files /dev/null and b/doc/fig15_.png differ diff --git a/doc/fig15b.png b/doc/fig15b.png new file mode 100644 index 0000000..15011de Binary files /dev/null and b/doc/fig15b.png differ diff --git a/doc/fig16.png b/doc/fig16.png new file mode 100644 index 0000000..682e2fd Binary files /dev/null and b/doc/fig16.png differ diff --git a/doc/fig17.png b/doc/fig17.png new file mode 100644 index 0000000..7426403 Binary files /dev/null and b/doc/fig17.png differ diff --git a/doc/fig18.png b/doc/fig18.png new file mode 100644 index 0000000..b2e4791 Binary files /dev/null and b/doc/fig18.png differ diff --git a/doc/fig19.png b/doc/fig19.png new file mode 100644 index 0000000..118bbe3 Binary files /dev/null and b/doc/fig19.png differ diff --git a/doc/fig2_movie.gif b/doc/fig2_movie.gif new file mode 100644 index 0000000..ab618c7 Binary files /dev/null and b/doc/fig2_movie.gif differ diff --git a/doc/fig3.png b/doc/fig3.png new file mode 100644 index 0000000..dfd3c8c Binary files /dev/null and b/doc/fig3.png differ diff --git a/doc/fig3b.png b/doc/fig3b.png new file mode 100644 index 0000000..364e776 Binary files /dev/null and b/doc/fig3b.png differ diff --git a/doc/fig4_movie.gif b/doc/fig4_movie.gif new file mode 100644 index 0000000..6dac21c Binary files /dev/null and b/doc/fig4_movie.gif differ diff --git a/doc/fig5.png b/doc/fig5.png new file mode 100644 index 0000000..d25da12 Binary files /dev/null and b/doc/fig5.png differ diff --git a/doc/fig6.png b/doc/fig6.png new file mode 100644 index 0000000..03bb0d1 Binary files /dev/null and b/doc/fig6.png differ diff --git a/doc/fig7.png b/doc/fig7.png new file mode 100644 index 0000000..da93b02 Binary files /dev/null and b/doc/fig7.png differ diff --git a/doc/fig8.png b/doc/fig8.png new file mode 100644 index 0000000..391c813 Binary files /dev/null and b/doc/fig8.png differ diff --git a/doc/fig9.png b/doc/fig9.png new file mode 100644 index 0000000..22c81bb Binary files /dev/null and b/doc/fig9.png differ diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..8310c1d --- /dev/null +++ b/src/README.md @@ -0,0 +1,14 @@ +# Arm's MRAM Simulation/Characterization Framework + +## Quick Start & More info +Full readme: [README.md](../README.md) +Summary: +* `test_sllgs_solver.py` shows you the basic s-LLGS solver config, calling (`sllgs_solver.py`) +* `stochastic_multithread_simulation.py` (calling `sllgs_solver.py`) is the script +that helps you launching parallel python s-LLGS simulations +* These results can be compared against Fooker-Plank simulations (see `plot_sllgs_fpe_comparison.py` script) +* `analytical.py` and `mtj_fp_fvm.py` contain the Fooker-Plank solvers. Analytical contains the WER/RER fitter for the problem optimization +* Verilog-a compact models: run the testbenches `tb.scs` and `tb_subckt.scs` + +Please, read the full description at [MRAM Framework Description](./doc/README.md). + diff --git a/src/fokker_plank/README.md b/src/fokker_plank/README.md new file mode 100644 index 0000000..83d7baf --- /dev/null +++ b/src/fokker_plank/README.md @@ -0,0 +1,20 @@ +## Quick Start & More info +Summary: +* Fooker-Plank simulations (see `plot_sllgs_fpe_comparison.py` script) `analytical.py` and `mtj_fp_fvm.py` + +Please, read the full description at [MRAM Framework Description](./doc/README.md). + + +## Files organization +* `doc` + * [README.md](./doc/README.md) for the **full MRAM framework description** +* `src` + * `fokker_plank` + * [README.md](./fokker_plank/README.md) for the MRAM Fokker-Plank description + * `fvm` + * `fvm_classes.py` Finite Volume Method classes, see [FVM](https://github.com/danieljfarrell/FVM) + * `mtj_fp_fvm.py` MTJ Fokker-Plank FVM solver + * `analytical` + * `analytical.py` MTJ Fokker-Plank Analytical solver + and WER/RER curves fitter + diff --git a/src/fokker_plank/analytical/analytical.py b/src/fokker_plank/analytical/analytical.py new file mode 100644 index 0000000..4d4afa9 --- /dev/null +++ b/src/fokker_plank/analytical/analytical.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) 2021-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Basic functions solving FPE. + +Based on +Tzoufras, M. (2018). +Switching probability of all-perpendicular spin valve nanopillars. +AIP Advances, 8(5). +https://doi.org/10.1063/1.5003832 + +FPE is expanded into Legendre pilynomials: +ρ(θ, τ) = sum[ r_n (τ) Pn(cos θ) +P_n(x) are the Legendre polynomials solution to eq (3) in [1] + +∂ρ/∂τ = sum[ sum [r_n a_{n+k, n} P_{n+k}] ] -> +∂r/∂τ = Ar⇒r(τ) = e^(Aτ)r(0) eq (11) at[1] +A is the pentadiagonal matrix with Legendre coefficients (at generate_A()) + + +Notes: +ρ area over theta should be 1/2pi, following [1] +as it integration should be over phi dimension as well in spherical coord +(i.e. int( 2*pi*f(theta)*sin(theta)d_theta ) +""" + +import numpy as np +import scipy as scipy +import scipy.special +from scipy.stats import maxwell +from scipy import optimize + +import fvm.mtj_fp_fvm as fvm +import python_compact_model.sllgs_solver as sllgs_solver + + +def sph_area_rho_over_theta(rho, theta, axis=-1): + """ + Compute the area of rho over theta in an spherical system. + + a) d_line_theta = sin(theta) dtheta + b) d_solid_angle = sin(theta) dtheta dphi + c) surface element in a surface of polar angle θ constant + (a cone with vertex the origin): + d_s_theta = r sin(theta) dphy dr + d) surface element in a surface of azimuth φ const (a vertical half-plane): + d_s_phi = r dr dtheta + Expects theta between [PI, 0] (comming from z in [-1, 1]). + """ + return np.abs(np.trapz(rho*np.sin(theta), x=theta)) + + +def generate_A(i, h, delta, j): + """Compute the pentadiagonal matrix.""" + # print(f'[debug] i: {i}, h: {h}, delta: {delta}') + field_diff = i - h + inv_2_delta = 1.0/(2.0*delta) + A = np.zeros([j+1, j+1], dtype=float) + # fill matrix + for n in range(2, j-1): + n2 = 2*n + A[n-2, n] = -1.0*n*(n-1)*(n-2)/((n2+1)*(n2-1)) + A[n-1, n] = field_diff*n*(n-1)/(n2+1) + A[n, n] = -1.0*n*(n+1)*(inv_2_delta-1/((n2+3)*(n2-1))) + A[n+1, n] = -1.0*field_diff*(n+1)*(n+2)/(n2+1) + A[n+2, n] = (n+1)*(n+2)*(n+3)/((n2+1)*(n2+3)) + # initial cases and last cases + # [0,0] = 0 + A[1, 0] = -1.0*field_diff*2 + A[2, 0] = 2.0 + # [0,1] = 0 + A[1, 1] = -2.0*(inv_2_delta - 1/5) + A[2, 1] = -1.0*field_diff*2 + A[3, 1] = 2*3*4/(3*5) + A[j-3, j-1] = -1.0*(j-1)*(j-2)*(j-3)/((2*j-1)*(2*j-3)) + A[j-2, j-1] = field_diff*(j-1)*(j-2)/(2*j-1) + A[j-1, j-1] = -1.0*(j-1)*j*(inv_2_delta - 1/((2*j+1)*(2*j-3))) + A[j, j-1] = -1.0*field_diff*j*(j+1)/(2*j-1) + A[j-2, j] = -1.0*j*(j-1)*(j-2)/((2*j+1)*(2*j-1)) + A[j-1, j] = field_diff*j*(j-1)/(2*j+1) + A[j, j] = -1.0*j*(j+1)*(inv_2_delta - 1/((2*j+3)*(2*j-1))) + + return A + + +def get_state(tau, i=None, h=None, delta=None, state_0=None, a_matrix=None): + """ + Compute the magnetization state. + + r(τ) = e^(Aτ)r(0) eq (11) at[1] + """ + if a_matrix is not None: + # get state from a known A matrix + # A matrix can be shared and it takes time to build + return np.matmul(scipy.linalg.expm(tau*a_matrix), state_0) + return np.matmul(scipy.linalg.expm( + tau*generate_A(i, h, delta, state_0.size-1)), state_0) + + +def untangle_state(m_state, + lin_space_z=False, + dim_points=2000): + """Untangle the Legendre series from its coefficients.""" + if lin_space_z: + z = np.linspace(-1, 1, dim_points) + theta = np.arccos(z) + return theta, np.polynomial.legendre.legval(z, m_state) + theta = np.linspace(np.pi, 0, dim_points) + return theta, np.polynomial.legendre.legval(np.cos(theta), m_state) + + +# @np.vectorize +def get_time_to_sw(a_matrix, s_rho_0, + rho_0_at_pi, + target_sw_prob=0.5, + target_tolerance=1e-3, + t_max=10, + do_manual_sw_prob=False, + sn=None): + """Compute time to switch for a given MTJ for a given A matrix.""" + dim_points = 10000 + max_tau_considered = 1e15 + max_iteration = int(1e2) + t_min = 0 + + # initialize integration matrix + if not do_manual_sw_prob and sn is None: + sn = get_sn(s_rho_0.shape[0]-1) + + # ensure switching + while True: + new_state = get_state(t_max, a_matrix=a_matrix, state_0=s_rho_0) + if do_manual_sw_prob: + theta, data = untangle_state(new_state, dim_points=dim_points) + sw_prob = compute_sw_prob(data=data, + theta=theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + sw_prob = 1 - get_analytical_sw_prob(new_state, sn) + if sw_prob > target_sw_prob: + break + if t_max > max_tau_considered: + print('tmax does not meet requirements') + return np.inf + t_max *= 10 + + iteration = 0 + while iteration < max_iteration: + t = (t_min + t_max)/2 + new_state = get_state(t, a_matrix=a_matrix, state_0=s_rho_0) + theta, data = untangle_state(new_state, dim_points=dim_points) + if do_manual_sw_prob: + theta, data = untangle_state(new_state, dim_points=dim_points) + sw_prob = compute_sw_prob(data=data, + theta=theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + sw_prob = 1 - get_analytical_sw_prob(new_state, sn) + if sw_prob < 0: + print(f'[error] negative sw t: {t}, sw_prob: {sw_prob}') + return np.inf + if np.abs(sw_prob - target_sw_prob) < target_tolerance: + print(f'\t\tfound t: {t}, sw prob: {sw_prob}') + return t + if iteration > 1 and iteration % 50 == 1: + print(f'iteration: {iteration}, t: {t}, sw_prob: {sw_prob}' + f' t_max: {t_max}, t_min: {t_min}') + if sw_prob < target_sw_prob: + t_min = t + else: + t_max = t + iteration += 1 + # max iterations + return t + + +def get_time_to_sw_fitting(c, delta, nu, alpha, h_k, + temperature=300, + rho_0_at_pi=False): + """ + Compute time to switch for a given MTJ/current for curve fitting. + + h=0 and only current/delta can vary. + parameters are: i_c, t_d, delta + """ + temperature = 300 + L0_max = 150 + lin_space_z = False + # i + _, _, s_rho_0 = get_state_rho_0(delta=delta, + do_maxwell=False, + L0=L0_max, + rho_0_at_pi=rho_0_at_pi, + lin_space_z=lin_space_z) + print(f'nu passed: {nu} alpha passed: {alpha} h_k: {h_k}') + i_c = delta*(4*alpha*sllgs_solver.c_E*sllgs_solver.c_KB * + temperature)/(nu*sllgs_solver.c_hbar) + t_d = (1+alpha*alpha)/(alpha*sllgs_solver.c_gamma_0*sllgs_solver.c_U0*h_k) + i = c/i_c + sw_t = np.zeros(i.shape) + # alpha = 0.02 + # nu = 0.3 + # temperature = 300 + # delta = nu*llg.c_hbar * i_c / (4*alpha*llg.c_E*llg.c_KB*temperature) + print(f'delta: {delta} for ic: {i_c}, t_d {t_d}') + for ii_idx, ii in enumerate(i): + # share A + A = generate_A(ii, 0.0, delta, s_rho_0.size-1) + sw_t[ii_idx] = get_time_to_sw(A, s_rho_0, rho_0_at_pi) + + sw_t *= t_d + if np.any(sw_t <= 0): + print('[error] negative sw time') + return -1 + + return sw_t + + +def basic_error_fn(error_mode, _get_time_to_sw_fitting, currents, _times): + """Specify basic error fn.""" + if error_mode == 0: + print('[fitting] Error mode 0: doing abs(err/x)^2') + + def err(p): return np.mean(( + (_get_time_to_sw_fitting(currents, *p)-_times)/_times)**2) + elif error_mode == 1: + print('[fitting] Error mode 1: doing abs(err)^2') + + def err(p): return np.mean(( + (_get_time_to_sw_fitting(currents, *p)-_times))**2) + elif error_mode == 2: + print('[fitting] Error mode 2: doing abs(err/x)') + + def err(p): return np.mean(np.abs( + (_get_time_to_sw_fitting(currents, *p)-_times)/_times)) + elif error_mode == 3: + print('[fitting] Error mode 3: doing abs(err)') + + def err(p): return np.mean(np.abs( + (_get_time_to_sw_fitting(currents, *p)-_times))) + return err + + +def _minimize_error(err, minimize_mode, p0, bounds): + """ + Perform minimization. + + Mode 0: global using basinhopping + Mode 1: global using shgo + Mode 2: global using brute force + Mode 3: local using minimize + """ + # choose minimization scheme + if minimize_mode == 0: + print('[fitting] Optimization algorithm: Basin Hopping') + # basinhopping for global minima + res = optimize.basinhopping(err, + p0, + # stepsize=0.5, + niter=500, + # accept_test=my_bounds, + minimizer_kwargs={'bounds': bounds} + ) + print(f'[res] Mode {minimize_mode}: {res}') + popt = res.x + return popt + elif minimize_mode == 1: + print('[fitting] Optimization algorithm: SHGO') + # shgo + res = optimize.shgo(err, + bounds=bounds, + iters=200) + print(f'[res] Mode {minimize_mode}: {res}') + popt = res.x + return popt + elif minimize_mode == 2: + print('[fitting] Optimization algorithm: Brute') + # shgo + res = optimize.brute(err, + ranges=bounds, + finish=optimize.fmin) + print(f'[res] Mode {minimize_mode}: {res}') + # if full_output is set to True + # res[0] has the params, res[1] the error evaluation + # otherwise: + popt = res + return popt + elif minimize_mode == 3: + print('[fitting] Optimization using local algorithm') + # minimize for local minima + res = optimize.minimize(err, + x0=p0, + bounds=bounds, + method='L-BFGS-B', + options={'eps': 1e-13}, + # method='dogbox', + ) + print(f'[res] Mode {minimize_mode}: {res}') + popt = res.x + return popt + + +def _fit_current_time_points(currents, + times, + rho_0_at_pi, + p0=None, + bounds=((0., 0.), (np.inf, np.inf)), + do_log=False, + minimize_mode=0, + error_mode=0): + """Fit current/time 2d array to i/h params.""" + # generate s_rho_0 + L0_max = 150 + lin_space_z = False + temperature = 300 + # define internal fn to not pass extra params like rho_0_at_pi + + # delta = 55 + # def _get_time_to_sw_fitting(c, nu, alpha, h_k): + def _get_time_to_sw_fitting(c, delta, nu, alpha, h_k): + """ + Compute time to switch for a given MTJ/current for curve fitting. + + h=0 and only current/delta can vary. + parameters are: delta, nu, alpha, h_k + """ + print(f'delta: {delta}, nu: {nu} alpha: {alpha} h_k: {h_k}') + _p = np.array([delta, nu, alpha, h_k]) + if np.any(np.isnan(_p)) or np.any(np.isinf(_p)): + print('[warning] optimizer passing NaN') + return np.nan + # initial state cannot be passed + _, _, s_rho_0 = get_state_rho_0(delta=delta, + do_maxwell=False, + L0=L0_max, + rho_0_at_pi=rho_0_at_pi, + lin_space_z=lin_space_z) + # i + i_c = delta*(4*alpha*sllgs_solver.c_E*sllgs_solver.c_KB * + temperature)/(nu*sllgs_solver.c_hbar) + t_d = (1+alpha*alpha) / \ + (alpha*sllgs_solver.c_gamma_0*sllgs_solver.c_U0*h_k) + i = c/i_c + sw_t = np.zeros(i.shape) + # alpha = 0.02 + # nu = 0.3 + # temperature = 300 + # delta = nu*llg.c_hbar * i_c / (4*alpha*llg.c_E*llg.c_KB*temperature) + print(f'delta: {delta} for ic: {i_c}, t_d {t_d}') + for ii_idx, ii in enumerate(i): + # share A + A = generate_A(ii, 0.0, delta, s_rho_0.size-1) + sw_t[ii_idx] = get_time_to_sw(A, s_rho_0, rho_0_at_pi) + + sw_t *= t_d + if np.any(sw_t <= 0): + print('[error] negative sw time') + return -np.inf + + if do_log: + return np.log(sw_t) + return sw_t + + _times = times + if do_log: + _times = np.log(times) + + # minimize approach + err = basic_error_fn(error_mode=error_mode, + _get_time_to_sw_fitting=_get_time_to_sw_fitting, + currents=currents, + _times=_times) + + # minimize + popt = _minimize_error(err, minimize_mode, p0, bounds) + + if do_log: + return popt, np.exp(_get_time_to_sw_fitting(currents, *popt)) + return popt, _get_time_to_sw_fitting(currents, *popt) + + +def get_nc(delta): + """Critical Nc.""" + return np.sqrt((delta/2)+1)-1/2 + + +def get_state_rho_0(delta, + L0=150, + rho_0_at_pi=False, + lin_space_z=False, + do_maxwell=False, + maxwell_loc=None, + maxwell_scale=None, + dim_points=2000): + """Generate the initial rho_0 distribution.""" + if lin_space_z: + # rho with equidistant z (fitting on z, so best option) + z = np.linspace(-1, 1, dim_points) + theta = np.arccos(z) + else: + # rho with equidistance theta + theta = np.linspace(np.pi, 0, dim_points) + z = np.cos(theta) + + if do_maxwell: + if maxwell_loc is None or maxwell_scale is None: + theta_0 = 1/np.sqrt(2*delta) + maxwell_scale = theta_0 + # theta = np.arccos(z) + # maxwell_loc = 0 # theta_0 + maxwell_loc = -theta_0 + # maxwell_loc = -theta_0/2 + # fitted + rho_0 = maxwell.pdf(theta, + loc=maxwell_loc, + scale=maxwell_scale) + else: + sin_theta = np.sin(theta) + rho_0 = np.exp(-delta*sin_theta*sin_theta)*np.heaviside(np.pi/2-theta, + 0.5) + # area over theta should be 1/2pi:, following + # Tzoufras, M. (2018). + # Switching probability of all-perpendicular spin valve nanopillars. + # AIP Advances, 8(5). + # https://doi.org/10.1063/1.5003832 + area = sph_area_rho_over_theta(rho_0, theta) + rho_0 /= (2*np.pi*area) + # flip if rho at pi + if rho_0_at_pi: + rho_0 = rho_0[::-1] + + # fit legendre coefficients + print(f'[debug] fitting to delta {delta}') + s_rho_0 = np.polynomial.legendre.legfit(x=z, + y=rho_0, + deg=L0) + + return z, rho_0, s_rho_0 + + +def get_sn(L0): + """ + Compute s_n vector from eq (12) in [1]. + + s_n = int_0^1 (P_n(x) dx) + """ + dtype = np.longdouble + # dtype = np.float + # print('using double precision and exact factorial2') + fact_type = np.int + print('using exact factorial2') + + sn = np.arange(L0+1, dtype=dtype) + sign = -1.0 * np.ones(sn[3::2].shape[0]).astype(dtype) + sign[1::2] = 1.0 + # factorials + nf = np.array([scipy.special.factorial2(f, exact=True) + for f in sn[3::2].astype(fact_type)]).astype(dtype) + nfm1 = np.array([scipy.special.factorial2(f, exact=True) + for f in sn[2:-1:2].astype(fact_type)]).astype(dtype) + # odd numbers + sn[3::2] = sign * nf / (sn[3::2]*(sn[3::2]+1)*nfm1) + # even numbers + sn[2::2] = 0.0 + # n==0 and n==1 + sn[0] = 1.0 + sn[1] = 0.5 + + return sn.astype(float) + + +def get_analytical_sw_prob(state, sn=None): + """Compute switching probability eq (12) in [1].""" + if sn is None: + sn = get_sn(state.shape[0]) + return np.dot(2.0*np.pi*sn, state) + + +def compute_sw_prob(data, theta, rho_0_at_pi, normalize=False): + """ + Compute switching probability with area on spherical coordinates. + + data is expected to be [time, theta] or [theta]. + FVM and legendre analytical compute over z. + Transformation is from int^1_-1 [ f(z)dz ] to + int^hemisphere [ f(theta) sin(theta) dtheta ]. + area is scalled by 2PI as the integral of rho should also be done + over unit phi vector between 0-2PI. + """ + if len(data.shape) == 1: + data = np.expand_dims(data, axis=0) + # area = sph_area_rho_over_theta(data, theta, axis=-1) + # print(f'[debug] area*2pi: {2*np.pi*area}') + # normalize not needed + # if normalize: + # area = sph_area_rho_over_theta(data, theta, axis=-1) + # print(f'[debug] area*2pi: {2*np.pi*area}') + # # print('[debug] area*2pi over theta ' + # # f'{2*np.pi*np.trapz(data, np.cos(theta))}') + # first_theta = theta[0] + # if first_theta > np.pi/2: + # # theta pi->0 + # # broadcast op with None + # data /= (-area[:, None]) + # else: + # # theta 0-> pi + # # broadcast op with None + # data /= area[:, None] + if rho_0_at_pi: + return 2*np.pi*sph_area_rho_over_theta(data[:, theta < np.pi/2], + theta[theta < np.pi/2]) + return 2*np.pi*sph_area_rho_over_theta(data[:, theta > np.pi/2], + theta[theta > np.pi/2]) + + +def get_sw_prob(s_rho_0, tau, delta, i, h, + rho_0_at_pi=False, + compute_fvm=False, + compute_analytical_sw=False, + compute_analytical_manual=False, + dim_points=500, + lin_space_z=False, + t_step=1e-3, + sn=None): + """ + Evolve FPE and compute switching probability over theta. + + FVM and legendre analytical compute over z. + Transformation is from int^1_-1 [ f(z)dz ] to + int^hemisphere [ f(theta) sin(theta) dtheta ]. + """ + # analytical rho computation + new_state = get_state(tau, i, h, delta, s_rho_0) + theta, data = untangle_state(new_state, dim_points=5000) + if compute_analytical_manual: + manual_prob = compute_sw_prob(data=data, + theta=theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + manual_prob = -1 + if compute_fvm: + theta, rho_init = untangle_state(s_rho_0, + dim_points=dim_points, + lin_space_z=lin_space_z) + rho_init = np.array(rho_init) + fvm_data = fvm.solve_mtj_fp(rho_init=rho_init, + delta=delta, + i0=i, + h=h, + T=tau, + dim_points=dim_points+1, + t_step=t_step, + do_3d=False, + lin_space_z=lin_space_z) + theta = np.arccos(fvm_data['z0']) + data = fvm_data['rho'] + fvm_prob = compute_sw_prob(data=data, + theta=theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + fvm_prob = -1 + if compute_analytical_sw: + analytical_prob = 1 - get_analytical_sw_prob(new_state, sn) + else: + analytical_prob = -1 + return {'manual_prob': manual_prob, + 'fvm_prob': fvm_prob, + 'analytical_prob': analytical_prob} + + +def get_sw_continuous_prob(s_rho_0, tau, delta, i, h, + rho_0_at_pi=False, + compute_fvm=False, + compute_analytical_sw=False, + compute_analytical_manual=False, + dim_points=1000, + lin_space_z=False, + t_step=1e-3, + sn=None): + """Evolve FPE and compute switching probability over time between 0-tau.""" + # analytical rho computation + time = np.arange(0, tau, t_step) + fvm_time = np.arange(0, tau, t_step) + manual_prob = np.zeros(time.shape[0]) + distribution = np.zeros((time.shape[0], dim_points)) + analytical_prob = np.zeros(time.shape[0]) + # share A + A = generate_A(i, h, delta, s_rho_0.size-1) + for t_idx, t in enumerate(time): + new_state = get_state(t, a_matrix=A, state_0=s_rho_0) + theta, data = untangle_state(new_state, dim_points=dim_points) + distribution[t_idx] = data + if compute_analytical_manual: + manual_prob[t_idx] = compute_sw_prob(data=data, + theta=theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + manual_prob[t_idx] = -1 + if compute_analytical_sw: + analytical_prob[t_idx] = 1 - get_analytical_sw_prob(new_state, sn) + else: + analytical_prob[t_idx] = -1 + fvm_theta = theta + if compute_fvm: + theta, rho_init = untangle_state(s_rho_0, + dim_points=dim_points, + lin_space_z=lin_space_z) + rho_init = np.array(rho_init) + fvm_data = fvm.solve_mtj_fp(rho_init=rho_init, + delta=delta, + i0=i, + h=h, + T=tau, + dim_points=dim_points+1, + t_step=t_step, + do_3d=True, + lin_space_z=lin_space_z) + fvm_theta = np.arccos(fvm_data['z0']) + data = fvm_data['rho'].T + fvm_time = fvm_data['t0'] + fvm_prob = compute_sw_prob(data=data, + theta=fvm_theta, + rho_0_at_pi=rho_0_at_pi, + normalize=True) + else: + fvm_prob = -1*np.ones(time.shape[0]) + return {'time': time, + 'theta': theta, + 'manual_prob': manual_prob, + 'pdf': distribution, + 'fvm_time': fvm_time, + 'fvm_theta': fvm_theta, + 'fvm_prob': fvm_prob, + 'analytical_prob': analytical_prob} diff --git a/src/fokker_plank/analytical/fvm b/src/fokker_plank/analytical/fvm new file mode 120000 index 0000000..2617479 --- /dev/null +++ b/src/fokker_plank/analytical/fvm @@ -0,0 +1 @@ +../fvm/ \ No newline at end of file diff --git a/src/fokker_plank/analytical/fvm_lib b/src/fokker_plank/analytical/fvm_lib new file mode 120000 index 0000000..f477d63 --- /dev/null +++ b/src/fokker_plank/analytical/fvm_lib @@ -0,0 +1 @@ +fvm/fvm_lib/ \ No newline at end of file diff --git a/src/fokker_plank/analytical/ivp_lib b/src/fokker_plank/analytical/ivp_lib new file mode 120000 index 0000000..347e797 --- /dev/null +++ b/src/fokker_plank/analytical/ivp_lib @@ -0,0 +1 @@ +python_compact_model/ivp_lib/ \ No newline at end of file diff --git a/src/fokker_plank/analytical/python_compact_model b/src/fokker_plank/analytical/python_compact_model new file mode 120000 index 0000000..acfa359 --- /dev/null +++ b/src/fokker_plank/analytical/python_compact_model @@ -0,0 +1 @@ +../../python_compact_model/ \ No newline at end of file diff --git a/src/fokker_plank/fvm/fvm_lib/fvm_classes.py b/src/fokker_plank/fvm/fvm_lib/fvm_classes.py new file mode 100644 index 0000000..4b69550 --- /dev/null +++ b/src/fokker_plank/fvm/fvm_lib/fvm_classes.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python +# coding: utf-8 + + +# Copyright (c) 2021. Daniel J. Farrell +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# adapted from Daniel J Farrell notes no FVM +# danieljfarrell.github.io/FVM/overview.html + + +"""Finite Volume Method Classes.""" + +import numpy as np +from scipy import sparse +from scipy.sparse import dia_matrix + + +# Supporting functions +def check_index_within_bounds(i, min_i, max_i): + """Check that the index (number or an iterable) is within the range.""" + return np.logical_and(i >= min_i, i <= max_i).all() + + +class Mesh(object): + """A 1D cell centered mesh defined by faces for the FVM.""" + + def __init__(self, faces): + """Init method.""" + super(Mesh, self).__init__() + + # Check for duplicated points + if len(faces) != len(set(faces)): + raise ValueError( + "The faces array contains duplicated positions." + "No cell can have zero volume so please" + " update with unique face positions.") + self.faces = np.array(faces) + self.cells = 0.5 * (self.faces[0:-1] + self.faces[1:]) + self.J = len(self.cells) + self.cell_widths = (self.faces[1:] - self.faces[0:-1]) + + def h(self, i): + """Return the width of the cell at the specified index.""" + return self.cell_widths[i] + + def hm(self, i): + """Distance between centroids in the backwards direction.""" + if not check_index_within_bounds(i, 1, self.J-1): + raise ValueError("hm index runs out of bounds") + return (self.cells[i] - self.cells[i-1]) + + def hp(self, i): + """Distance between centroids in the forward direction.""" + if not check_index_within_bounds(i, 0, self.J-2): + raise ValueError("hp index runs out of bounds") + return (self.cells[i+1] - self.cells[i]) + + +class CellVariable(np.ndarray): + """ + Representation of a variable defined at the cell centers. + + Provides interpolation functions to calculate the value at cell faces. + """ + # http://docs.scipy.org/doc/numpy/user/basics.subclassing.html + def __new__(cls, input_array, mesh=None): + """Init like method.""" + # If `input_array` is actually just a constant + # convert it to an array of len the number of cells. + try: + len(input_array) + except TypeError: + input_array = input_array*np.ones(len(mesh.cells)) + + obj = np.asarray(input_array).view(cls) + obj.mesh = mesh + return obj + + def __array_finalize__(self, obj): + """Build like method.""" + if obj is None: + return + self.mesh = getattr(obj, 'mesh', None) + self.__get_items__ = getattr(obj, '__get_items__', None) + + def m(self, i): + """ + Linear interpolation of the cell value at the right hand face . + + i.e. along the _m_inus direction. + """ + return (self.mesh.h(i)/(2*self.mesh.hm(i))*self[i-1] + + self.mesh.h(i-1)/(2*self.mesh.hm(i))*self[i]) + + def p(self, i): + """ + Linear interpolation of the cell value at the right hand face. + + i.e. along the _p_lus direction. + """ + return (self.mesh.h(i+1)/(2*self.mesh.hp(i))*self[i] + + self.mesh.h(i)/(2*self.mesh.hp(i))*self[i+1]) + + +class AdvectionDiffusionModel(object): + """A model for the advection-diffusion equation.""" + + def __init__(self, faces, a, d, k, discretization="central"): + """Init.""" + super(AdvectionDiffusionModel, self).__init__() + + self.mesh = Mesh(faces) + self.a = CellVariable(a, mesh=self.mesh) + self.d = CellVariable(d, mesh=self.mesh) + self.t_step = k + self.discretization = discretization + + # Check Peclet number + mu = self.peclet_number() + mu_max = np.max(np.abs(mu)) + if mu_max >= 1.5 and mu_max < 2.0: + print(f'\n\nThe Peclet number is {mu_max},' + ' this is getting close to the limit of mod 2.' + '\nINCREASE precision on spatial axis') + # warnings.warn('The Peclet number is %g,' + # ' this is getting close to the limit of mod 2.') + elif mu_max > 2: + print(f'\n\nThe Peclet number {mu_max} has exceeded the maximum' + ' value of mod 2 for the central discretization scheme.' + '\nINCREASE precision on spatial axis') + # warnings.warn( + # 'The Peclet number %g has exceeded the maximum' + # ' value of mod 2 for the central discretization scheme.', + # mu_max) + + # Check CFL condition + CFL = self.CFL_condition() + CFL_max = np.max(np.abs(CFL)) + if CFL_max > 0.5 and CFL_max < 1.0: + print(f'\n\n[WARNING] The CFL condition is {CFL_max}', + 'it is getting close to the upper limit.' + '\nINCREASE precision on time axis') + # warnings.warn('[WARNING] The CFL condition is %g', + # 'it is getting close to the upper limit.', + # CFL_max) + elif np.max(np.abs(CFL)) > 1: + print(f'\n\n[WARNING] The CFL condition is {CFL_max},' + 'and has gone above the upper limit.' + '\nINCREASE precision on time axis') + # warnings.warn('[WARNING] The CFL condition is %g,' + # 'and has gone above the upper limit.', + # CFL_max) + + if discretization == 'exponential': + self.kappa = (np.exp(mu) + 1)/(np.exp(mu) - 1) - 2/mu + self.kappa[np.where(mu == 0.0)] = 0 + self.kappa[np.where(np.isposinf(mu))] = 1 + self.kappa[np.where(np.isneginf(mu))] = -1 + elif discretization == 'upwind': + kappa_neg = np.where(self.a < 0, -1, 0) + kappa_pos = np.where(self.a > 0, 1, 0) + self.kappa = kappa_neg + kappa_pos + elif discretization == 'central': + self.kappa = np.zeros(self.mesh.J) + else: + print('Please set "discretization" to one of the following:' + '"upwind", "central" or "exponential"') + + # Artificially modify the diffusion coefficient + # to introduce adpative discretization + self.d = self.d + 0.5 * self.a * self.mesh.cell_widths * self.kappa + print(f'kappa min:{np.min(self.kappa)}, max:{np.max(self.kappa)}') + # print(f'kappa: {self.kappa}') + + def peclet_number(self): + """Get Peclet number.""" + return self.a * self.mesh.cell_widths / self.d + + def CFL_condition(self): + """Get CFL condition.""" + return self.a * self.t_step / self.mesh.cell_widths + + def set_boundary_conditions(self, + left_flux=None, + right_flux=None, + left_value=None, + right_value=None): + """ + Boundary conditions. + + Make sure this function is used + sensibly otherwise the matrix will be ill posed. + """ + self.left_flux = left_flux + self.right_flux = right_flux + self.left_value = left_value + self.right_value = right_value + + def _interior_matrix_elements(self, i): + """Set interior coefficients for matrix equation.""" + def ra(i, a, d, m): + return 1./m.h(i) * (a.m(i)*m.h(i)/(2*m.hm(i)) + d.m(i)/m.hm(i)) + + def rb(i, a, d, m): + return 1./m.h(i)*( + a.m(i)*m.h(i-1)/(2*m.hm(i)) - + a.p(i)*m.h(i+1)/(2*m.hp(i)) - d.m(i)/m.hm(i) - d.p(i)/m.hp(i)) + + def rc(i, a, d, m): + return 1./m.h(i) * (-a.p(i)*m.h(i)/(2*m.hp(i)) + d.p(i)/m.hp(i)) + + return (ra(i, self.a, self.d, self.mesh), + rb(i, self.a, self.d, self.mesh), + rc(i, self.a, self.d, self.mesh)) + + def _neumann_boundary_c_m_e_left(self): + """Set Left hand side Neumann boundary conditions.""" + def b1(a, d, m): + return 1./m.h(0) * (-a.p(0)*m.h(1)/(2*m.hp(0)) - d.p(0)/m.hp(0)) + + def c1(a, d, m): + return 1./m.h(0) * (-a.p(0)*m.h(0)/(2*m.hp(0)) + d.p(0)/m.hp(0)) + + # Index and element value + locations = [(0, 0), (0, 1)] + values = (b1(self.a, self.d, self.mesh), + c1(self.a, self.d, self.mesh)) + return tuple([list(x) for x in zip(locations, values)]) + + def _neumann_boundary_c_m_e_right(self, matrix=None): + """Set right hand side Neumann boundary conditions.""" + def aJ(a, d, m): + return 1./m.h(m.J-1)*( + a.m(m.J-1) * m.h(m.J-1)/(2*m.hm(m.J-1)) + + d.m(m.J-1)/m.hm(m.J-1)) + + def bJ(a, d, m): + return 1./m.h(m.J-1)*( + a.m(m.J-1) * m.h(m.J-2)/(2*m.hm(m.J-1)) + - d.m(m.J-1)/m.hm(m.J-1)) + # Index and element value + J = self.mesh.J + + # Index and element value + locations = [(J-1, J-2), (J-1, J-1)] + values = (aJ(self.a, self.d, self.mesh), + bJ(self.a, self.d, self.mesh)) + return tuple([list(x) for x in zip(locations, values)]) + + def _neumann_boundary_c_v_e_left(self): + """Index and boundary cond vector elements for Neumann conditions.""" + location = [0] + value = [self.left_flux/self.mesh.h(0)] + return tuple([list(x) for x in zip(location, value)]) + + def _neumann_boundary_c_v_e_right(self): + """Index and boundary cond. vector elements for Neumann conditions.""" + location = [self.mesh.J-1] + value = [-self.right_flux/self.mesh.h(self.mesh.J-1)] + return tuple([list(x) for x in zip(location, value)]) + + def _dirichlet_boundary_c_m_e_left(self): + """Set left hand side Neumann boundary coefficients for matrix eq.""" + def rb(i, a, d, m): + return 1./m.h(i)*( + a.m(i)*m.h(i-1)/(2*m.hm(i)) + - a.p(i)*m.h(i+1)/(2*m.hp(i)) + - d.m(i)/m.hm(i) + - d.p(i)/m.hp(i)) + + def rc(i, a, d, m): + return 1./m.h(i) * (-a.p(i)*m.h(i)/(2*m.hp(i)) + d.p(i)/m.hp(i)) + + # Index and element value + locations = [(0, 0), (0, 1)] + # values = ( rb(0, self.a, self.d, self.mesh ), + # rc(0, self.a, self.d, self.mesh ) ) + values = (0, + 1) + return tuple([list(x) for x in zip(locations, values)]) + + def _dirichlet_boundary_c_m_e_right(self): + """Set right hand side Neumann boundary coefficients for matrix eq.""" + def ra(i, a, d, m): + return 1./m.h(i) * (a.m(i)*m.h(i)/(2*m.hm(i)) + d.m(i)/m.hm(i)) + + def rb(i, a, d, m): + return 1./m.h(i)*( + a.m(i)*m.h(i-1)/(2*m.hm(i)) + - a.p(i)*m.h(i+1)/(2*m.hp(i)) + - d.m(i)/m.hm(i) + - d.p(i)/m.hp(i)) + J = self.mesh.J # Index and element value + + # Index and element value + locations = [(J-1, J-2), (J-1, J-1)] + # values = ( ra(self.J-1, self.a, self.d, self.mesh ), + # rb(self.J-1, self.a, self.d, self.mesh ) ) + values = (0, + 1) + return tuple([list(x) for x in zip(locations, values)]) + + def _dirichlet_boundary_c_v_e_left(self): + """ + Index and boundary condition vector elements for Dirichlet conditions. + + NB these are always zero, unless BCs are time varying. + """ + location = [0] + value = [0] + return tuple([list(x) for x in zip(location, value)]) + + def _dirichlet_boundary_c_v_e_right(self): + """ + Index and boundary condition vector elements for Dirichlet conditions. + + NB these are always zero, unless BCs are time varying. + """ + location = [self.mesh.J-1] + value = [0] + return tuple([list(x) for x in zip(location, value)]) + + def alpha_matrix(self): + """ + Set alpha matrix. + + The alpha matrix is used to mask boundary conditions values + for Dirichlet conditions. Otherwise for a fully Neumann (or Robin) + system it is equal to the identity matrix. + """ + a1 = 0 if self.left_flux is None else 1 + aJ = 0 if self.left_flux is None else 1 + diagonals = np.ones(self.mesh.J) + diagonals[0] = a1 + diagonals[-1] = aJ + return sparse.diags(diagonals, 0) + + def beta_vector(self): + """Return the Neumann boundary condition vector.""" + b = np.zeros(self.mesh.J) + + if self.left_flux is not None: + left_bc_elements = self._neumann_boundary_c_v_e_left() + + if self.right_flux is not None: + right_bc_elements = self._neumann_boundary_c_v_e_right() + + if self.left_value is not None: + left_bc_elements = self._dirichlet_boundary_c_v_e_left() + + if self.right_value is not None: + right_bc_elements = self._dirichlet_boundary_c_v_e_right() + + bcs = left_bc_elements + right_bc_elements + for inx, value in bcs: + b[inx] = value + return b + + def coefficient_matrix(self): + """Return the coeff matrix which appears on the left hand side.""" + J = self.mesh.J + # k = self.k + # m = self.mesh + # a = self.a + # d = self.d + + # A element which is pushed off the + # edge of the matrix by the spdiags function + padding = np.array([0]) + # Yes, its the same. But this element + # is included in the matrix (semantic difference). + zero = padding + # one = np.array([1]) # + + if self.left_flux is not None: + left_bc_elements = self._neumann_boundary_c_m_e_left() + + if self.right_flux is not None: + right_bc_elements = self._neumann_boundary_c_m_e_right() + + if self.left_value is not None: + left_bc_elements = self._dirichlet_boundary_c_m_e_left() + + if self.right_value is not None: + right_bc_elements = self._dirichlet_boundary_c_m_e_right() + + # Use the functions to layout the matrix Note that the boundary + # condition elements are set to zero, they are filled in as + # the next step. + inx = np.array(list(range(1, J-1))) + ra, rb, rc = self._interior_matrix_elements(inx) + # c1 + upper = np.concatenate([padding, zero, rc]) + + # b1 bJ + central = np.concatenate([zero, rb, zero]) + + # aJ + lower = np.concatenate([ra, zero, padding]) + + A = sparse.spdiags([lower, central, upper], [-1, 0, 1], J, J).todok() + + # Apply boundary conditions elements + bcs = left_bc_elements + right_bc_elements + for inx, value in bcs: + print(f'boundary conditions. A[{inx}]={value}') + A[inx] = value + return dia_matrix(A) diff --git a/src/fokker_plank/fvm/mtj_fp_fvm.py b/src/fokker_plank/fvm/mtj_fp_fvm.py new file mode 100644 index 0000000..0a6dbfd --- /dev/null +++ b/src/fokker_plank/fvm/mtj_fp_fvm.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python + +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +MTJ Fokker-Plank Finite Volume Method Solver. + +Fokker-Plank or advection-diffusion for +MTJ magnetization probability evolution. +""" + +import numpy as np +from scipy import sparse +from scipy.sparse import linalg +import matplotlib.pyplot as plt +import matplotlib.cm as cm + +import fvm_lib.fvm_classes as fvm + +np.random.seed(seed=1) + + +def solve_mtj_fp(dim_points=1000, + rho_init=None, + delta=60, + i0=1.5, + h=0., + t_step=0.001, + T=10, + lin_space_z=False, + do_3d=True): + """Solve FVM for the FPE on a perpendicular symetric MTJ.""" + # cranck-nickolson + theta = 0.5 + # fully implicit + # theta = 1. + + # discretization + discretization = 'exponential' + # discretization = 'upwind' + # discretization = 'central' + + # Dirichlet boundary conditions + # right_value = 1.0 + # left_value = 1.0 + # Neumann boundary conditions + left_flux = 0.0 + right_flux = 0.0 + + if lin_space_z: + # linear in z + faces = np.linspace(-1, 1, dim_points) + else: + # linear in theta + faces = np.cos(np.linspace(np.pi, 0, dim_points)) + mesh = fvm.Mesh(faces) + + # drift/advection/convection + U = (i0-h-mesh.cells)*(1-mesh.cells*mesh.cells) + # diffusion + D = (1-mesh.cells*mesh.cells)/(2*delta) + + # drift/advection/convection + a = fvm.CellVariable(-U, mesh=mesh) + # diffusion + d = fvm.CellVariable(D, mesh=mesh) + # Source term + # s[int(np.median(range(mesh.J)))] = 0.0 + + # Initial conditions + if rho_init is None: + # w_init = np.exp( + # -delta*(1-mesh.cells*mesh.cells))*np.heaviside(mesh.cells, 0.5) + _theta_x = np.arccos(mesh.cells) + rho_init = np.exp( + -delta*np.sin(_theta_x)*np.sin(_theta_x))*np.heaviside(mesh.cells, + 0.5) + rho_init /= np.trapz(rho_init, x=mesh.cells) + # w_init = w_init[::-1] + print(f'\trho_init area: {np.trapz(rho_init, x=mesh.cells)}') + + model = fvm.AdvectionDiffusionModel( + faces, a, d, t_step, discretization=discretization) + # model.set_boundary_conditions(left_value=1., right_value=0.) + model.set_boundary_conditions(left_flux=left_flux, right_flux=right_flux) + M = model.coefficient_matrix() + alpha = model.alpha_matrix() + beta = model.beta_vector() + identity = sparse.identity(model.mesh.J) + print(f'\talpha: [{np.min(alpha.todense())}, {np.max(alpha.todense())}]') + print(f'\tbeta: [{np.min(beta)}, {np.max(beta)}]') + print(f'\tidentity: {identity.shape}') + + # Construct linear system from discretised matrices, A.x = d + A = identity - t_step*theta*alpha*M + d = (identity + t_step*(1-theta)*alpha*M)*rho_init + beta + + print("\tPeclet number", np.min(model.peclet_number()), + np.max(model.peclet_number())) + print("\tCFL condition", np.min(model.CFL_condition()), + np.max(model.CFL_condition())) + + rho = rho_init + + t0 = np.linspace(0, T, int(T/t_step)+1) + if do_3d: + rho = np.zeros((t0.shape[0], mesh.cells.shape[0])) + area = np.zeros(t0.shape[0]) + rho[0] = rho_init + area[0] = np.trapz(rho[0], x=mesh.cells) + for i in range(1, t0.shape[0]): + d = (identity + t_step*(1-theta)*alpha*M)*rho[i-1] + beta + rho[i] = linalg.spsolve(A, d) + # normalization not needed, flux is kept + # PS/PNS + ps = np.trapz(rho.T[mesh.cells < 0], + x=mesh.cells[mesh.cells < 0], axis=0) + t_sw = t0[np.argmax(ps > 0.5)] + else: + rho = np.array(rho_init) + rho_next = np.array(rho_init) + t_sw = 0 + for i in range(1, t0.shape[0]): + d = (identity + t_step*(1-theta)*alpha*M)*rho + beta + rho_next = linalg.spsolve(A, d) + # normalization not needed, flux is kept + ps = np.trapz(rho.T[mesh.cells < 0], + x=mesh.cells[mesh.cells < 0], + axis=0) + if t_sw == 0 and ps > 0.5: + t_sw = t_step*i + # update variable by switching + rho_next, rho = rho, rho_next + + # return + return {'t_sw': t_sw, + 'rho': rho.T, + 't0': t0, + 'z0': mesh.cells} + + +def simple_test(): + """FVM simple test.""" + z_points = 500 + delta = 60 + i0 = 1.5 + h = 0. + # time step + t_step = 0.001 + T = 20 + + data = solve_mtj_fp(dim_points=z_points, + delta=delta, + i0=i0, + h=h, + t_step=t_step, + T=T) + rho = data['rho'] + t0 = data['t0'] + z0 = data['z0'] + _theta_x = np.arccos(z0) + t_mesh0, z_mesh0 = np.meshgrid(t0, z0) + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + # z_mesh0 = np.arccos(z_mesh0) + ax.plot_surface(z_mesh0, + t_mesh0, + rho, + alpha=0.7, + cmap='viridis', + edgecolor='k') + plt.show() + + print('plotting 2d') + fig = plt.figure() + ax_0 = fig.add_subplot(211) + ax_1 = fig.add_subplot(212) + + fixed_list = [0, 1, 2, 4, 8, 14, T] + for tt_idx, tt in enumerate(fixed_list): + t0_idx = np.argmax(t0 >= tt) + if tt == t0[-1]: + t0_idx = -1 + ax_0.plot(_theta_x, rho[:, t0_idx], label=f't={tt}') + ax_1.plot(z0, rho[:, t0_idx], label=f't={tt}') + ax_0.legend() + ax_0.set_yscale('log', base=10) + ax_1.legend() + ax_1.set_yscale('log', base=10) + # ax.set_ylim(bottom=1e-10) + ax_0.grid() + ax_1.grid() + plt.show() + + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + # colors + name = "tab10" + cmap = cm.get_cmap(name) # type: matplotlib.colors.ListedColormap + colors = cmap.colors # type: list + # plot_3d_evolution(p_imp_0, t0, z0, plot_res=1e6, + # title='ro implicit matmult') + pt = np.trapz(rho, z0, axis=0) + ps = np.trapz(y=rho[z0 < 0], x=z0[z0 < 0], axis=0)/pt + pns = np.trapz(y=rho[z0 >= 0], x=z0[z0 >= 0], axis=0)/pt + + ax.plot(t0, + ps, + '--', + color=colors[0], + alpha=0.5, + label=f'ps i: {i0}') + ax.plot(t0, + pns, + label=f'pns i: {i0}') + ax.set_yscale('log', base=10) + # ax.set_ylim(bottom=1e-10) + ax.legend() + ax.grid() + plt.show() + + +if __name__ == '__main__': + simple_test() diff --git a/src/python_compact_model/README.md b/src/python_compact_model/README.md new file mode 100644 index 0000000..56875dd --- /dev/null +++ b/src/python_compact_model/README.md @@ -0,0 +1,87 @@ + + +* [Quick Start & More info](#quick-start--more-info) +* [Files organization](#files-organization) +* [s-LLGS Solvers](#s-llgs-solvers) + * [No-thermal or emulated-thermal simulations](#no-thermal-or-emulated-thermal-simulations) + * [Thermal Stochastic Simulations](#thermal-stochastic-simulations) + + +## Quick Start & More info +Full readme: [README.md](../README.md) + +Summary: +* `test_sllgs_solver.py` shows you the basic s-LLGS solver config, calling (`sllgs_solver.py`) +* `stochastic_multithread_simulation.py` (calling `sllgs_solver.py`) is the script +that helps you launching parallel python s-LLGS simulations +* These results can be compared against Fooker-Plank simulations (see `plot_sllgs_fpe_comparison.py` script) +* `analytical.py` and `mtj_fp_fvm.py` contain the Fooker-Plank solvers. Analytical contains the WER/RER fitter for the problem optimization +* Verilog-a compact models: run the testbenches `tb.scs` and `tb_subckt.scs` + +Please, read the full description at [MRAM Framework Description](./doc/README.md). + +**IMPORTANT: Before using the compact models**, read the [s-LLGS Solvers](#s-llgs-solvers) info. + + +## Files organization +* `doc` + * [README.md](./doc/README.md) for the **full MRAM framework description** +* `src` + * `python_compact_model` + * [README.md](./python_compact_model/README.md) for the MRAM python s-LLGS description + * `sllgs_solver.py` Python s-LLGS solver + * `stochastic_multithread_simulation.py` Multi-thread stochastic simulations + * `test_sllgs_solver.py` Simple s-LLGS tests + * `ode_solver_custom_fn.py` *solve_ivp* auxilar fns derived from Scipy + +## s-LLGS Solvers + +### No-thermal or emulated-thermal simulations +* Use Scipy solver in python (`scipy_ivp=True`) +* Use Spherical coordinates +* Control the simulation through tolerances (`atol, rtol` in python) + +``` + # No thermal, no fake_thermal, solved with scipy_ivp + llg_a = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_a = llg_a.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + rtol=1e-4, + atol=1e-9) + # No thermal, EMULATED THERMAL, solved with scipy_ivp + llg_b = sllg_solver.LLG(do_fake_thermal=True, + d_theta_fake_th=1/30, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_b = llg_b.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + max_step=1e-11, + rtol=1e-4, + atol=1e-9) +``` +### Thermal Stochastic Simulations +Require stochastic differential equation solvers +* Use SDE solvers in python (`scipy_ivp=False`) +* Use Cartesian coordinates +* Control the simulation through maximum time step (`max_step` in python) +``` + llg_c = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=True, + i_amp_fn=current, + seed=seed_0) + data_c = llg_c.solve_and_plot(10e-9, + scipy_ivp=False, + solve_spherical=False, + solve_normalized=True, + max_step=1e-13, + method='stratonovich_heun') +``` +![MRAM Magnetization and stochasticity](./doc/fig4_movie.gif) diff --git a/src/python_compact_model/ivp_lib/ode_solver_custom_fn.py b/src/python_compact_model/ivp_lib/ode_solver_custom_fn.py new file mode 100644 index 0000000..6428d90 --- /dev/null +++ b/src/python_compact_model/ivp_lib/ode_solver_custom_fn.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Scipy. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# The following fns are modifications from Scipy +# rk.py and base.py files Latest commit a534a56 on Aug 18, 2020 + +""" +Auxiliar functions for solve_ivp problem. + +Required to pass dt to the integration function. +""" + +import numpy as np + + +def ode_solver_init_custom(self, fun, t0, y0, t_bound, vectorized, + support_complex=False): + """Replace for OdeSolver __init__ passing h to the wrappers.""" + self.t_old = None + self.t = t0 + self._fun, self.y = check_arguments_custom(fun, y0, support_complex) + self.t_bound = t_bound + self.vectorized = vectorized + + if vectorized: + def fun_single(t, y, h): + return self._fun(t, y[:, None], h).ravel() + fun_vectorized = self._fun + else: + fun_single = self._fun + + def fun_vectorized(t, y, h): + f = np.empty_like(y) + for i, yi in enumerate(y.T): + f[:, i] = self._fun(t, yi, h) + return f + + def fun(t, y, h=1e-10): + self.nfev += 1 + return self.fun_single(t, y, h) + + self.fun = fun + self.fun_single = fun_single + self.fun_vectorized = fun_vectorized + + self.direction = np.sign(t_bound - t0) if t_bound != t0 else 1 + self.n = self.y.size + self.status = 'running' + + self.nfev = 0 + self.njev = 0 + self.nlu = 0 + + +def check_arguments_custom(fun, y0, support_complex): + """Helper function for checking arguments common to all solvers.""" + y0 = np.asarray(y0) + if np.issubdtype(y0.dtype, np.complexfloating): + if not support_complex: + raise ValueError("`y0` is complex, but the chosen solver does " + "not support integration in a complex domain.") + dtype = complex + else: + dtype = float + y0 = y0.astype(dtype, copy=False) + + if y0.ndim != 1: + raise ValueError("`y0` must be 1-dimensional.") + + def fun_wrapped(t, y, dt=1e-10): + return np.asarray(fun(t, y, dt), dtype=dtype) + + return fun_wrapped, y0 + + +def rk_step_custom(fun, t, y, f, h, A, B, C, K): + """Perform a single Runge-Kutta step. + + This function computes a prediction of an explicit Runge-Kutta method and + also estimates the error of a less accurate method. + + Notation for Butcher tableau is as in [1]_. + + Parameters + ---------- + fun : callable + Right-hand side of the system. + t : float + Current time. + y : ndarray, shape (n,) + Current state. + f : ndarray, shape (n,) + Current value of the derivative, i.e., ``fun(x, y)``. + h : float + Step to use. + A : ndarray, shape (n_stages, n_stages) + Coefficients for combining previous RK stages to compute the next + stage. For explicit methods the coefficients at and above the main + diagonal are zeros. + B : ndarray, shape (n_stages,) + Coefficients for combining RK stages for computing the final + prediction. + C : ndarray, shape (n_stages,) + Coefficients for incrementing time for consecutive RK stages. + The value for the first stage is always zero. + K : ndarray, shape (n_stages + 1, n) + Storage array for putting RK stages here. Stages are stored in rows. + The last row is a linear combination of the previous rows with + coefficients + + Returns + ------- + y_new : ndarray, shape (n,) + Solution at t + h computed with a higher accuracy. + f_new : ndarray, shape (n,) + Derivative ``fun(t + h, y_new)``. + + References + ---------- + .. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential + Equations I: Nonstiff Problems", Sec. II.4. + """ + # print(f'fn: {fun}') + + K[0] = f + for s, (a, c) in enumerate(zip(A[1:], C[1:]), start=1): + dy = np.dot(K[:s].T, a[:s]) * h + K[s] = fun(t + c * h, y + dy, c * h) + + y_new = y + h * np.dot(K[:-1].T, B) + f_new = fun(t + h, y_new, h) + + K[-1] = f_new + + return y_new, f_new + + +def rk_step_custom_circular(fun, t, y, f, h, A, B, C, K): + """Perform a single Runge-Kutta step. + + This function computes a prediction of an explicit Runge-Kutta method and + also estimates the error of a less accurate method. + + Notation for Butcher tableau is as in [1]_. + + Parameters + ---------- + fun : callable + Right-hand side of the system. + t : float + Current time. + y : ndarray, shape (n,) + Current state. + f : ndarray, shape (n,) + Current value of the derivative, i.e., ``fun(x, y)``. + h : float + Step to use. + A : ndarray, shape (n_stages, n_stages) + Coefficients for combining previous RK stages to compute the next + stage. For explicit methods the coefficients at and above the main + diagonal are zeros. + B : ndarray, shape (n_stages,) + Coefficients for combining RK stages for computing the final + prediction. + C : ndarray, shape (n_stages,) + Coefficients for incrementing time for consecutive RK stages. + The value for the first stage is always zero. + K : ndarray, shape (n_stages + 1, n) + Storage array for putting RK stages here. Stages are stored in rows. + The last row is a linear combination of the previous rows with + coefficients + + Returns + ------- + y_new : ndarray, shape (n,) + Solution at t + h computed with a higher accuracy. + f_new : ndarray, shape (n,) + Derivative ``fun(t + h, y_new)``. + + References + ---------- + .. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential + Equations I: Nonstiff Problems", Sec. II.4. + """ + # print(f'fn: {fun}') + + def _circular_theta(m): + # circular operator on theta + if m[1] > np.pi: + m[1] = m[1] - np.pi + elif m[1] < 0: + m[1] = - m[1] + return m + + K[0] = f + # circular operator on theta + K[0] = _circular_theta(K[0]) + for s, (a, c) in enumerate(zip(A[1:], C[1:]), start=1): + dy = np.dot(K[:s].T, a[:s]) * h + K[s] = fun(t + c * h, y + dy, c * h) + # circular operator on theta + K[s] = _circular_theta(K[s]) + + y_new = y + h * np.dot(K[:-1].T, B) + f_new = fun(t + h, y_new, h) + # circular operator on theta + f_new = _circular_theta(f_new) + + K[-1] = f_new + + return y_new, f_new diff --git a/src/python_compact_model/sllgs_solver.py b/src/python_compact_model/sllgs_solver.py new file mode 100644 index 0000000..8b8acee --- /dev/null +++ b/src/python_compact_model/sllgs_solver.py @@ -0,0 +1,1627 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +s-LLGS Solver for MRAM Magnetization. + +Computation of LLGS equation following OOMMF Oxs_SpinXferEvolve +https://math.nist.gov/oommf/doc/userguide20a2/userguide/Standard_Oxs_Ext_Child_Clas.html +Each one of the field components computation +follows the reference defined in the respective method. +""" + +import time +import numpy as np +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from scipy.integrate._ivp import rk, base +from scipy.integrate import solve_ivp +from scipy.stats import maxwell + +import ivp_lib.ode_solver_custom_fn as custom_ode + +# gyromagnetic ratio +c_gamma_0 = 1.76e11 # [rad/s/T] +# https://www.ctcms.nist.gov/~rdm/mumag.org.html +# equivalent to c_U0*2.211e5 (ommited often [rad]) * [m/C] = ([rad]) * [ m/A/s] + +# elementary charge, [C] +c_E = 1.602176462e-19 +# reduced plank's constant [J s] +c_hbar = 1.054571817e-34 +# vacuum permeability, 4 pi / 1e7 [H/m] +c_U0 = 1.25663706212e-6 +# boltzmann's constant [J/K] +c_KB = 1.3806503e-23 +# Borh magneton [J/T] +c_UB = c_gamma_0 * c_hbar / 2 + +# custom fitting +loc_theta_0_coeff = 1/3. + + +def eq_info(): + """Equation info.""" + print('\t* Considered energies:') + print('\t\ta) Zeeman energy (due to external field)') + print('\t\tb) Uniaxial anisotropy energy (interfacial, see bellow)') + print('\t\tc) Thermal energy') + print('\t\td) Shape anisotropy energy') + print('\n\t\tZeeman energy, uniaxial anisotropy and thermal energy') + print('\t\tcontribute to Hext.') + + print('\n\t* Full anisotropy:') + print('\tWatanabe, K., ') + print('\tShape anisotropy revisited in single-digit nanometer magnetic') + print('\ttunnel junctions.') + print('\tNature Communications, 9(1), 5–10.') + print('\thttps://doi.org/10.1038/s41467-018-03003-7') + + print('\n\t* Computation of LLGS equation following' + 'OOMMF Oxs_SpinXferEvolve') + print('\thttps://math.nist.gov/oommf/doc/userguide20a2/userguide/' + 'Standard_Oxs_Ext_Child_Clas.html') + print('\t with the extra in-plane terms') + print('\t see OOMMF for the value of the different.') + + print('\n\tLLG equation (with all stt terms) at eq 1.3') + print('\tSwitching distributions for perpendicular spin-torque devices') + print('\twithin the macrospin approximation.') + print('\tIEEE Transactions on Magnetics, 48(12), 4684–4700.') + print('\thttps://doi.org/10.1109/TMAG.2012.2209122') + + print('\t# (1+alpha^2)dm/dt = #LLG ') + print('\t# # Gilbert damping model: ') + print('\t# -gamma c_U0 m^H_eff') + print('\t# # Gilbert damping model: ') + print('\t# -alpha gamma c_U0 m^m^H_eff') + print('\t# # T_CPP (torque current perp. plane) ') + print('\t# # Slonczewski Model: ') + print('\t# -gamma nu I_ c_hbar / (2 c_E V) m^m^mp') + print('\t# +alpha gamma c_hbar f(I_)/ (2 c_E V) m^m^mp') + print('\t# # T_CIP (torque current in plane) Zhang-Li') + print('\t# +gamma nu f(I_) c_hbar / (2 c_E V) m^mp') + print('\t# +alpha gamma nu I_ c_hbar / (2 c_E V) m^mp') + + print('\n\t-> normalized and grouping by I_ (h_stt) and f(I_) (h_stt_2):') + print('\t# h_eff = H_eff / (gamma c_U0 Ms)') + print('\t# Ns = 2 Ms V / (gamma c_hbar)') + print('\t# I = Is / (c_E gamma c_U0 Ms Ns)') + print('\t# = 2 Js / (c_E c_U0 Ms t_fl area)') + print('\t# (1+alpha^2)dm/dt = -m^h_eff -alpha*m^m^h_eff') + print('\t# -m^m^h_stt +alpha*m^h_stt') + print('\t# +m^h_stt_2 +alpha*m^m^h_stt_2') + print('\n\t-> spherical') + print('\tAment, S., et al.') + print('\tSolving the stochastic Landau-Lifshitz-Gilbert-Slonczewski') + print('\tequation for monodomain nanomagnets :') + print('\tA survey and analysis of numerical techniques.') + print('\tRetrieved from http://arxiv.org/abs/1607.04596') + print('\t# (1+alpha^2)dtheta/dt = [') + print('\t# h_eff_phi + alpha*h_eff_theta') + print('\t# + h_stt_1_theta - alpha*h_stt_phi') + print('\t# - h_stt_2_phi - alpha*h_stt_2_theta') + print('\t# ]') + print('\t# (1+alpha^2)dphi/dt = [') + print('\t# - h_eff_theta + alpha*h_eff_phi') + print('\t# + h_stt_1_phi + alpha*h_stt_1_theta') + print('\t# + h_stt_2_theta - alpha*h_stt_2_phi') + print('\t# ]/sin(theta)') + + print('\n\t* Noise') + print('\tPinna, D., et. al.') + print('\tSpin-transfer torque magnetization reversal in uniaxial') + print('\tnanomagnets with thermal noise.') + print('\tJournal of Applied Physics, 114(3), 1–9.') + print('\thttps://doi.org/10.1063/1.4813488') + + +def normalize_cart(u): + """Normalize a cartesian vector u.""" + return u / np.sqrt(np.sum(u**2)) + + +def uxv_cart(u, v): + """U x V in cartesians.""" + w_x = (u[1]*v[2] - v[1]*u[2]) + w_y = (-u[0]*v[2] + u[2]*v[0]) + w_z = (u[0]*v[1] - u[1]*v[0]) + return np.array([w_x, w_y, w_z]) + + +def cart_from_spherical_fn(sph_vector): + """ + Convert a vector from spherical to cartesian. + + sph_vector is [idx][ro, theta, phi] + or [rho, theta, phi] + """ + if len(sph_vector.shape) != 2: + sph_vector = np.expand_dims(sph_vector, axis=0) + expanded = True + else: + expanded = False + xyz_vector = np.zeros(sph_vector.shape) + xyz_vector[:, 0] = sph_vector[:, 0] * \ + np.sin(sph_vector[:, 1])*np.cos(sph_vector[:, 2]) + xyz_vector[:, 1] = sph_vector[:, 0] * \ + np.sin(sph_vector[:, 1])*np.sin(sph_vector[:, 2]) + xyz_vector[:, 2] = sph_vector[:, 0] * np.cos(sph_vector[:, 1]) + if expanded: + return xyz_vector[0] + return xyz_vector + + +def spherical_from_cart_np(xyz_vector): + """ + Convert a vector from cart to spherical. + + cart_vector is [idx][x, y, z] + """ + if len(xyz_vector.shape) != 2: + xyz_vector = np.expand_dims(xyz_vector, axis=0) + expanded = True + else: + expanded = False + sph_vector = np.zeros(xyz_vector.shape) + xy = xyz_vector[:, 0]**2 + xyz_vector[:, 1]**2 + sph_vector[:, 0] = np.sqrt(xy + xyz_vector[:, 2]**2) + # for elevation angle defined from Z-axis down + sph_vector[:, 1] = np.arctan2(np.sqrt(xy), xyz_vector[:, 2]) + # for elevation angle defined from XY-plane up + # sph_vector[:,1] = np.arctan2(xyz_vector[:,2], np.sqrt(xy)) + sph_vector[:, 2] = np.arctan2(xyz_vector[:, 1], xyz_vector[:, 0]) + if expanded: + return sph_vector[0] + return sph_vector + + +def oommf_test(t): + """Return test on OOMMF.""" + # return np.array([0.0, 0.0, 0.0]) + return np.array([0.0, 0.0, 2e6]) + + +def zero3(t): + """Return array of zeros.""" + return np.array([0.0, 0.0, 0.0]) + + +def zero(t): + """Return default function for current.""" + return 0.0 + + +def def_window_power(d_theta, theta, theta_min, p=20): + """ + "Windowing fn for theta. + + Requires theta between [0, np.pi], not an open integration. + """ + def _window(x): + # power window + return 1. - np.power(x - 1, p) + + # required if theta_ini bellow theta_min + if theta > np.pi/2 and d_theta < 0: + return 1. + if theta < np.pi/2 and d_theta > 0: + return 1. + delta = theta_min/3 + if theta > np.pi/2 and np.pi - theta >= theta_min: + return _window(-theta + (np.pi - theta_min - delta)) + if theta <= np.pi/2 and theta >= theta_min: + return _window(theta - theta_min - delta) + else: + return 0. + + +def def_window(d_theta, theta, theta_min): + """ + "Windowing fn for theta. + + Requires theta between [0, np.pi], not an open integration. + """ + def _window(x): + # Tukey window + if x < theta_min/4: + return 0.5*(1 - np.cos(2*np.pi*x / (theta_min/2))) + return 1. + + # required if theta_ini bellow theta_min + if theta > np.pi/2 and d_theta < 0: + return 1. + if theta < np.pi/2 and d_theta > 0: + return 1. + if theta > np.pi/2 and np.pi - theta >= theta_min: + return _window(-theta + (np.pi - theta_min)) + if theta <= np.pi/2 and theta >= theta_min: + return _window(theta - theta_min) + else: + return 0. + + +class Sol: + """Solution class.""" + + def __init__(self): + pass + + +class LLG: + """Basic s-LLGS class.""" + + def __init__(self, + hk_mode=1, + do_thermal=False, # simulate thermal noise + do_theta_windowing=False, # force a min theta angle + # (set to theta_init) if + # thermal is not computed, + # to avoid total damping + do_fake_thermal=True, # simulate fake thermal noise + d_theta_fake_th=1/15, # d_theta will have an extra + # term based on this + theta_windowing=def_window, # theta windowing function + # should min theta be forced + theta_windowing_factor=2, # theta_min = factor*theta_0 + seed=0, + i_amp_fn=zero, # [A] applied current f(t) + # in P direction + temperature=300, # [K] + t_fl=1.0e-9, # [m] thickness of free layer + t_ox=1.6e-9, # [m] thickness of oxide + w_fl=50e-9, # [m] mtj width + l_fl=50e-9, # [m] mtj length + # init state + # order of priority: m_init, angle_init, state + # init state priority 0 + m_init=None, # initial m vector state + # [x, y, z], + # init state priority 1 + # only used if m_init is None + theta_init=None, # [rad] None 1/sqrt(2delta(T)) + phi_init=0.1*np.pi/180, # [rad] + # init state priority 2 + # only used if m_init and angle_init are None + state='P', # Initial state, only used if + # theta_init=None + + # magnetic parameters + k_i_0=1e-3, # uniaxial anisotropy [J/m^2] + alpha=0.005, # alpha damping factor + ms=0.97e6, # magnetization sat. [A/m] + # shape anisotropy + shape_ani_demag_mode=2, # 0: no shape anisotropy + # 1: shape_ani_demag_n vector + # 2 and 3: + # two different literature + # implementations + shape_ani_demag_n=None, # Shape anisotropy demag vector + # if None, it is computed + # as a cylinder + # exchange energy + do_a_exchange=False, # whether do or not energy + # exchange + a_exchange=1e-11, # Exchange constant [J/m] + + # stt parameters + stt_mode='stt_oommf_simple', # stt_oommf_full: + # lamb. & P free per layer + # stt_oommf_simple: + # single lambda and P + # stt_simplest: just p + # stt mode oomf_full + p_pl=0.3, # polarization factor + p_fl=0.25, # polarization factor + lambda_pl=1.2, # [FITTING] parameter + lambda_fl=1.2, # [FITTING] parameter + # for out of plane torque + # stt mode oomf_simple + lambda_s=1.2, # [FITTING] parameter + p=0.35, # polarization factor + # stt mode simplest + nabla_mult=1, # epsilon = nabla/2 + # secondary STT term + eps_prime=0.0, # [FITTING] constant + + # vcma paramters + do_vcma=False, + xi=61e-15, # vcma coeff, [J/(V*m)] + + # TMR and conductance + r_p=6e3, # RP resistance, [ohm] + + # pinned layer angle + theta_pl=0.0, # [rad] pinned layer theta + phi_pl=0.0, # [rad] pinned layer phi + h_ext_cart=zero3 # external field f(t) [A/m] + ): + """Init Method.""" + # parameters + + # random seed + # deprecated, use RandomState instead + # np.random.seed(seed) + self.seed = seed + self.rng = np.random.default_rng(seed) + print(f'[seed] seed initialized: {seed}') + + self.hk_mode = hk_mode + print(f'\t[debug] hk_mode: {hk_mode}') + + # thermal + self.do_thermal = do_thermal + self.do_theta_windowing = do_theta_windowing + self.theta_windowing = theta_windowing + self.theta_windowing_factor = theta_windowing_factor + self.do_fake_thermal = do_fake_thermal + self.d_theta_fake_th = d_theta_fake_th + if not self.do_thermal and self.do_theta_windowing: + # Convergence issues if not properly initialized + print('WARNING Convergence issues if not properly initialized') + + # achieved switching time + self.switching_t = np.inf + + # current + self.i_amp_fn = i_amp_fn + self.t_i_idx = 0 + # temperature + self.temperature = temperature + # self.temperature_nom = 25 # [Celsius] not used, + # conductance not implemented + # dimensions + self.t_fl = t_fl + self.t_ox = t_ox + self.w_fl = w_fl + self.l_fl = l_fl + self._area = np.pi/4*self.w_fl*self.l_fl + self._volume = self._area*self.t_fl + + # magnetic properties + self.alpha = alpha + self._U0_gamma_0_alpha = c_U0 * c_gamma_0 / (1 + self.alpha*self.alpha) + self.ms = ms + # Full anisotropy: + # Watanabe, K., + # Shape anisotropy revisited in single-digit nanometer magnetic + # tunnel junctions. + # Nature Communications, 9(1), 5–10. + # https://doi.org/10.1038/s41467-018-03003-7 + self.k_i = k_i_0 + # we consider just interfacial term + self.k_u = self.k_i / self.t_fl + self._init_shape_anisotropy_n(shape_ani_demag_mode, shape_ani_demag_n) + + self.do_a_exchange = do_a_exchange + self.a_exchange = a_exchange + if a_exchange <= 0: + print('[warning] A energy exchange disabled') + self.a_exchange = 0. + # Thermal Stability + # initial h_k with approximate m_cart = [0, 0, +/-1] + if m_init is not None: + z_0 = m_init[2] + elif theta_init is not None: + z_0 = np.cos(theta_init) + else: + if state == 'P': + z_0 = 1 + else: + z_0 = -1 + print(f'z_0: {z_0}') + h_k = 2 * self.k_i * np.abs(z_0) / (self.t_fl * c_U0 * self.ms) + if self.hk_mode == 0: + h_k += -self.ms * \ + (self.shape_ani_demag_n[2] - + self.shape_ani_demag_n[0]) * np.abs(z_0) + else: + h_k += -self.ms*(self.shape_ani_demag_n[2]) * np.abs(z_0) + # print(f'fer: hk: {h_k}') + # self.m_cart_init = [0, 0, z_0] + # self.theta_0 = np.arccos(z_0) + # print(f'fer: hkb: {self._get_h_k_eff()}') + self.thermal_stability = c_U0 * self.ms * h_k * self._volume / ( + 2 * c_KB * self.temperature) + + # 'internal parameters' + # Internal parameters are computed here for efficiency + # directly from the OOMMF reference in Purdue's manual + + # For the STT term + # for out of plane torque + self.eps_prime = eps_prime # [FITTING] constant + self._init_stt_constants(stt_mode=stt_mode, + # oommf full mode + lambda_pl=lambda_pl, + lambda_fl=lambda_fl, + p_pl=p_pl, + p_fl=p_fl, + # oommf_simple mode + lambda_s=lambda_s, + p=p, + # simple mode + # p=p, # shared with oommf_simple + nabla_mult=nabla_mult) + + # vcma + self.do_vcma = do_vcma + self.xi = xi + + # pinned layer magnetization vector, p + # used in spin torque transfer + self.theta_pl = theta_pl + self.phi_pl = phi_pl + + # thermal noise: + self._init_thermal_constants() + + # initialize magnetic vectors + self._init_magnetic_vectors(m_init, theta_init, phi_init, state) + + # external field + self.h_ext_cart = h_ext_cart + + # conductance + self.r_p = r_p + self.tmr = 2*self.p*self.p/(1-self.p*self.p) + print(f'\t[debug] p: {self.p}') + print(f'\t[debug] tmr: {self.tmr}') + + # debug ic, taud + self._get_Ic(debug=True) + self._get_tau_d(debug=True) + + # time handling + self.last_t = 0.0 + self.last_dt = 1e-12 + + def _init_shape_anisotropy_n(self, + shape_ani_demag_mode, + shape_ani_demag_n): + """Init shape anisotropy constants.""" + self.shape_ani_demag_n = shape_ani_demag_n + if shape_ani_demag_mode == 0: + self.shape_ani_demag_n = np.zeros(3) + elif shape_ani_demag_mode == 1: + if np.sum(shape_ani_demag_n) != 1.: + print('[warning] sum(shape_ani_demag_n) != 1,' + f'sum: {np.sum(shape_ani_demag_n)}') + self.shape_ani_demag_n = shape_ani_demag_n + elif shape_ani_demag_mode == 2: + # ferromagnetic cylinder + # Sato, M., & Ishii, Y. (1989). + # Simple and approximate expressions of demagnetizing + # factors of uniformly magnetized rectangular + # rod and cylinder. + # Journal of Applied Physics, 66(2), 983–985. + # https://doi.org/10.1063/1.343481 + r = np.sqrt(self.w_fl*self.l_fl)/2 + nz = 1/(2*self.t_fl/(r*np.sqrt(np.pi)) + 1) + self.shape_ani_demag_n = np.array([ + (1-nz)/2, + (1-nz)/2, + nz]) + elif shape_ani_demag_mode == 3: + # Zhang, K., et al. + # Compact Modeling and Analysis of Voltage-Gated + # Spin-Orbit Torque Magnetic Tunnel Junction. + # IEEE Access, 8, 50792–50800. + # https://doi.org/10.1109/ACCESS.2020.2980073 + self.shape_ani_demag_n = np.array([ + np.pi*self.t_fl / (4*np.sqrt(self.w_fl*self.l_fl)), + np.pi*self.t_fl / (4*np.sqrt(self.w_fl*self.l_fl)), + 1 - 2 * np.pi*self.t_fl/(4*np.sqrt(self.w_fl*self.l_fl))]) + else: + print('[error] shape_ani_demag_n not supported') + exit(1) + self.shape_ani_demag_n = np.array(self.shape_ani_demag_n) + print(f'\t[debug] shape_ani mode {shape_ani_demag_mode}. ' + f'shape_ani_n: {self.shape_ani_demag_n}') + + def _init_stt_constants(self, + stt_mode, + # oommf full mode + lambda_pl, + lambda_fl, + p_pl, + p_fl, + # oommf_simple mode + lambda_s, + p, + # simple mode + # p=p, # shared with oommf_simple + nabla_mult): + """ + Initialize STT constants following OOMMF. + + a) OOMMFC, where lambda_pl != lambda_fl + see full equation at + https://kelvinxyfong.wordpress.com/research/research-interests + /oommf-extensions/oommf-extension-xf_thermspinxferevolve/ + b) OOMMFC, where lambda_pl == lambda_fl == lambda + c) stt_simplest: + Khvalkovskiy, A. V., et. al. + Basic principles of STT-MRAM cell operation in memory arrays. + Journal of Physics D: Applied Physics, 46(7), 074001. + https://doi.org/10.1088/0022-3727/46/7/074001 + """ + self.stt_mode = stt_mode + self._stt_scale_mult = 1 # 1 matching OOMMF + self._stt_scale_fac = self._stt_scale_mult * \ + c_hbar / (c_U0 * c_E * self.ms * self._volume) + if self.stt_mode == 'stt_oommf_full': + self.lambda_pl = lambda_pl + self.lambda_fl = lambda_fl + _lamb_PL2 = lambda_pl*lambda_pl + _lamb_FL2 = lambda_fl*lambda_fl + self._ap = np.sqrt(_lamb_PL2+1)*(_lamb_FL2+1) + self._am = np.sqrt(_lamb_PL2-1)*(_lamb_FL2-1) + self._ap2 = (_lamb_PL2+1)*(_lamb_FL2+1) + self._am2 = (_lamb_PL2-1)*(_lamb_FL2-1) + + self._kp = p_pl*_lamb_PL2 * \ + np.sqrt((_lamb_FL2+1)/(_lamb_PL2+1)) + p_fl * \ + _lamb_FL2*np.sqrt((_lamb_PL2-1)/(_lamb_FL2-1)) + self._km = p_pl*_lamb_PL2 * \ + np.sqrt((_lamb_FL2+1)/(_lamb_PL2+1)) - p_fl * \ + _lamb_FL2*np.sqrt((_lamb_PL2-1)/(_lamb_FL2-1)) + elif self.stt_mode == 'stt_oommf_simple': + self.lambda_s = lambda_s + self.p = p + self._eps_simple_num = p*lambda_s*lambda_s + self._eps_simple_den0 = lambda_s*lambda_s + 1 + self._eps_simple_den1 = lambda_s*lambda_s - 1 + elif self.stt_mode == 'stt_simplest': + self._nabla_mult = nabla_mult # epsilon = nabla/2 + # nabla = _nabla_mult * P/(1+/-P^2) + # so epsilon = _nabla_mult/2 * P/(1+/-P^2) + # see model code, different research groups + # use different factors for SMTJ + self.p = p + else: + print('[ERROR] stt_mode should be ' + '"stt_oommf_full" | "stt_oommf_simple" | "stt_simplest"' + f' Provided: {self.stt_mode}') + raise Exception + + def _init_thermal_constants(self): + """Init thermal constants. + + Equation from newest Purdue's paper + Torunbalci, M. M., et. al (2018). + Modular Compact Modeling of MTJ Devices. + IEEE Transactions on Electron Devices, 65(10), 4628–4634. + https:#doi.org/10.1109/TED.2018.2863538 + and + De Rose, R., et al. + A Variation-Aware Timing Modeling Approach for Write Operation + in Hybrid CMOS/STT-MTJ Circuits. + IEEE Transactions on Circuits and Systems I: Regular Papers, + 65(3), 1086–1095. + https:#doi.org/10.1109/TCSI.2017.2762431 + Please, + note it is different from: + Ament, S., Rangarajan, N., Parthasarathy, A., & Rakheja, S. (2016). + Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski equation + for monodomain nanomagnets : A survey and analysis + of numerical techniques. 1–19. + http://arxiv.org/abs/1607.04596 + + We also include the (1+alpha^2) effect from + Lee, H., Lee, A., Wang, S., Ebrahimi, F., Gupta, + P., Khalili Amiri, P., & Wang, K. L. (2018). + Analysis and Compact Modeling of Magnetic Tunnel Junctions + Utilizing Voltage-Controlled Magnetic Anisotropy. + IEEE Transactions on Magnetics, 54(4). + https://doi.org/10.1109/TMAG.2017.2788010 + + Also, note that the field generated later is + sqrt(2 alpha temp Kb / ((1+alpha^2) u0 gamma Ms V dt)) + """ + # Units: A/m -> (note gamma = c_gamma_0*c_U0) + # sqrt[ (J/K) * K / / ((H/m) * (rad/s/T) * (H/m) * (A/m) * (m^3)) ] + # = A/m sqrt[s / rad]) + # which is later multiplied by sqrt[1/s] so the final + # sigma_th = A/m / sqrt(rad) + th_gamma = c_gamma_0 * c_U0 / (1 + self.alpha*self.alpha) + self.th_power_noise_std = np.sqrt( + 2. * self.alpha * self.temperature * c_KB / + (c_U0 * th_gamma * self.ms * self._volume)) + + # for the non-normalized field: + # Units: sqrt(J/((Henry/m)*m*m*m)) = A/m + self.sigma_th = np.sqrt( + 2. * self.alpha * self.temperature * c_KB / + (c_U0 * self._volume)) + # for the normalized field: + # Units: sqrt(J/((Henry/m)*(A/m)*(A/m)*m*m*m)) = 1 + # self.sigma_th = np.sqrt( + # 2. * self.alpha * self.temperature * c_KB / + # (c_U0 * self.ms * self.ms * self._volume)) + + def _init_magnetic_vectors(self, m_init, theta_init, + phi_init, state): + """ + Initialize magnetic vectors. + + we could add 1ns of stabilization before injecting current + or, by adding noise with gaussian/maxwell-boltzmann distribution, + where the second moment gives + the thermal average (theta_0 square) + + # Maxwell-Boltzmann distribution info: + a) Switching distributions for perpendicular spin-torque devices + within the macrospin approximation. + IEEE Transactions on Magnetics, 48(12), 4684–4700. + https://doi.org/10.1109/TMAG.2012.2209122 + b) Khvalkovskiy, A. V., et. al. + Basic principles of STT-MRAM cell operation in memory arrays. + Journal of Physics D: Applied Physics, 46(7), 074001. + https://doi.org/10.1088/0022-3727/46/7/074001 + + theta_0 can be given by 1/sqrt(2*delta) (most common aproach) + a) Sun, J. Z. (2006). + Spin angular momentum transfer in current-perpendicular + nanomagnetic junctions. IBM Journal of Research and Development + https://doi.org/10.1147/rd.501.0081< + Butler, W. H., et al. + b) Switching distributions for perpendicular spin-torque devices + within the macrospin approximation. + IEEE Transactions on Magnetics, 48(12), 4684–4700. + https://doi.org/10.1109/TMAG.2012.2209122 + + or 1/sqrt(delta) + c) Khvalkovskiy, A. V., et al. + Basic principles of STT-MRAM cell operation in memory arrays. + Journal of Physics D: Applied Physics, 46(7), 074001. + https://doi.org/10.1088/0022-3727/46/7/074001 + """ + # initial angles under no variability + print(f'\t[debug] thermal_stability: {self.thermal_stability}') + self.theta_0 = np.sqrt(1/(2*self.thermal_stability)) + print(f'\t[debug] theta_0: {self.theta_0}, or {np.pi-self.theta_0}') + print(f'\t[debug] m_init: {m_init}') + if m_init is not None: + print(f'\t[debug] m_init given: {m_init}') + _, self.theta_init, self.phi_init = spherical_from_cart_np(m_init) + if np.isnan(self.theta_init) or self.theta_init == 0: + self.theta_init = 0.01 + if np.isnan(self.phi_init): + self.phi_init = 0.01 + elif theta_init is None: + # initial free layer angle + # mean_angle = maxwell.stats( + # moments='m', + # loc=-self.theta_0*loc_theta_0_coeff, + # scale=self.theta_0, + # ) + # _mean = np.sqrt(2/np.pi)*2*self.theta_0 \ + # - self.theta_0*loc_theta_0_coeff + _mode = np.sqrt(2)*self.theta_0 - self.theta_0*loc_theta_0_coeff + if state == 'P': + self.theta_init = _mode + elif state == 'AP': + self.theta_init = np.pi - _mode + else: + print(f'[error] invalid state [allowed: P|AP]: {state}') + return None + # phi_init + if phi_init is None: + self.phi_init = self.rng.uniform(0.0, 2*np.pi) + else: + self.phi_init = phi_init + else: + self.theta_init = theta_init + # phi_init + if phi_init is None: + self.phi_init = self.rng.uniform(0.0, 2*np.pi) + else: + self.phi_init = phi_init + + # for windowing + self.theta_min = self.theta_windowing_factor * self.theta_0 + # force min theta even when passed initial params + # in case it is out of boundaries + if self.do_theta_windowing: + if (self.theta_init > np.pi/2 and + np.pi-self.theta_init < self.theta_min): + self.theta_init = np.pi-self.theta_min + elif self.theta_init < self.theta_min: + self.theta_init = self.theta_min + + # debug + print(f'\t[debug] initial theta_init : {self.theta_init}') + print(f'\t[debug] initial phi_init : {self.phi_init}') + + # initial angle variability, + # f noise is present and an initial angle has not been passed + # we allow random noise from outside via theta_init + if self.do_thermal and theta_init is None: + # get from maxwell distribution + # generate an object as the np seed might be overrriden + _maxwell_rv = maxwell( + loc=-self.theta_0*loc_theta_0_coeff, + scale=self.theta_0) + _maxwell_rv.random_state = self.rng + # np.random.RandomState(seed=self.seed) + rand_angle = _maxwell_rv.rvs(size=1)[0] + # while(rand_angle < 0): + # rand_angle = maxwell.rvs( + # loc=-self.theta_0*loc_theta_0_coeff, + # size=1, + # scale=self.theta_0)[0] + # if self.seed % 2 == 1: + # rand_angle = -rand_angle + print(f'\t[debug] rand_angle: {rand_angle}') + # other distributions, should them be desired + # rand_inc = np.random.normal(0., + # 0.1/np.sqrt(2*self.thermal_stability), + # 1)[0] + if (self.theta_init > np.pi/2 and + np.pi-rand_angle < self.theta_0): + self.theta_init = np.pi-rand_angle + elif rand_angle < self.theta_0: + self.theta_init = rand_angle + print(f'\t[debug] theta_init after random: {self.theta_init}') + + # init vectors + self.m_sph_init = np.array([1, self.theta_init, self.phi_init]) + self.m_cart_init = cart_from_spherical_fn(self.m_sph_init) + self.p_cart = np.array([np.sin(self.theta_pl)*np.cos(self.phi_pl), + np.sin(self.theta_pl)*np.sin(self.phi_pl), + np.cos(self.theta_pl)]) + print(f'\t[debug] constructor m_cart_init : {self.m_cart_init}') + print(f'\t[debug] constructor m_shp_init : {self.m_sph_init}') + + def _get_Ic(self, debug=False): + """Get I critical with no VCMA.""" + eps_0 = self.get_epsilon_stt(np.dot( + self.m_cart_init, self.p_cart)) + # effective out of plane component + hk_eff = self._get_h_k_eff() + # nabla is 2 eps + ic = 2*self.alpha*c_E*c_U0*hk_eff*self.ms*self._volume/(c_hbar*eps_0*2) + if debug: + print(f'\t[debug] eps_0: {eps_0}') + print(f'\t[debug] hk_eff: {hk_eff}') + print(f'\t[debug] Ic: {ic}') + return ic + + def _get_h_k_eff(self, debug=True): + """Get H_k initial with no VCMA.""" + # effective out of plane component + m_0 = np.array(self.m_cart_init) + # h_k_eff = self.get_h_demag(m_0)[2] + self.get_h_una(m_0)[2] + # if debug: + # print(f'\t[debug] H_k: {h_k_eff}') + if self.hk_mode == 0: + k_u_eff = self.k_i / self.t_fl - \ + 0.5*c_U0 * ( + self.shape_ani_demag_n[2] - + self.shape_ani_demag_n[0])*self.ms*self.ms + else: + k_u_eff = self.k_i / self.t_fl - \ + 0.5*c_U0 * (self.shape_ani_demag_n[2])*self.ms*self.ms + h_k_eff = 2 * k_u_eff * m_0[2] / (c_U0 * self.ms) + + # m_0[2] = np.cos(self.theta_0) + # h_k_eff = self.get_h_demag(m_0)[2] + self.get_h_una(m_0)[2] + + return h_k_eff + + def _get_tau_d(self, debug=False): + """Get H_k initial with no VCMA.""" + hk_eff = self._get_h_k_eff() + tau_d = (1 + self.alpha*self.alpha)/( + self.alpha * c_gamma_0 * c_U0 * hk_eff) + if debug: + print(f'\t[debug] hk_eff: {hk_eff}') + print(f'\t[debug] tau_d: {tau_d}') + return tau_d + + def get_epsilon_stt(self, mdp): + """Compute OOMMF epsilon term based on mdp vector. + + a) OOMMFC, where lambda_pl != lambda_fl + b) OOMMFC, where lambda_pl == lambda_fl == lambda + c) stt_simplest: + Khvalkovskiy, A. V., et. al. + Basic principles of STT-MRAM cell operation in memory arrays. + Journal of Physics D: Applied Physics, 46(7), 074001. + https://doi.org/10.1088/0022-3727/46/7/074001 + """ + if self.stt_mode == 'stt_oommf_full': + if (self.lambda_pl == 1.0) or (self.lambda_fl == 1.0): + return 0.5 * self.p + else: + return self._kp / (self._ap + self._am * mdp) + \ + self._km / (self._ap - self._am * mdp) + elif self.stt_mode == 'stt_oommf_simple': + return self._eps_simple_num / ( + self._eps_simple_den0 + self._eps_simple_den1 * mdp) + elif self.stt_mode == 'stt_simplest': + return self._nabla_mult/2*self.p/(1 + self.p * self.p * mdp) + print('[error] Non-valid stt_mode "stt_oommf_full" | ' + '"stt_oommf_simple" | "stt_simplest"') + return None + + ################### + # H_ext components + ################### + + def get_h_exchange(self): + """ + Get energy exchange field. + + https://www.iue.tuwien.ac.at/phd/makarov/dissertationch5.html + """ + # for now return 0, (grad^2 * m) + if not self.do_a_exchange: + return 0. + return 2*self.a_exchange/(c_U0 * self.ms) + + def get_h_th(self, dt): + """Get thermal var. + + Note that the field generated is a Brownian motion problem. + Brownian motion can be simulated realizing that + dW = W_j - W_{j-1} ~ N(0, dt) = sqrt(dt) N(0, 1) + We return sigma/sqrt(dt) as that term will later be multiplied + by dt by the solver, and dW = sigma*sqrt(dt). + """ + if not self.do_thermal or dt == np.inf: + return np.zeros(3) + # _rnd = self.rng.normal(0., + # self.th_power_noise_std / np.sqrt(dt), + # 3) + _rnd = self.rng.normal(0., + 1, + 3) + _rnd = normalize_cart(_rnd) + return _rnd * self.th_power_noise_std / np.sqrt(dt) + + def get_h_demag(self, m_cart): + """Get H_demag field vector due to shape anisotropy in the FL. + + Zhang, K., et. al. + Compact Modeling and Analysis of Voltage-Gated Spin-Orbit + Torque Magnetic Tunnel Junction. + IEEE Access, 8, 50792–50800. + https://doi.org/10.1109/ACCESS.2020.2980073 + + Full anisotropy: + Watanabe, K., + Shape anisotropy revisited in single-digit nanometer magnetic + tunnel junctions. + Nature Communications, 9(1), 5–10. + https://doi.org/10.1038/s41467-018-03003-7 + """ + # given [nx, ny, xz] + return -self.ms*self.shape_ani_demag_n * m_cart + + def get_h_una(self, m_cart): + """Get uniaxial anisotropy vector. + + We consider interfacial PMA anisotropy. + The geometry defines it. + See Figure 2 at + Khvalkovskiy, A. V., et. al. + Basic principles of STT-MRAM cell operation in memory arrays. + Journal of Physics D: Applied Physics, 46(7), 074001. + https://doi.org/10.1088/0022-3727/46/7/074001 + + Full anisotropy: + Watanabe, K., + Shape anisotropy revisited in single-digit nanometer magnetic + tunnel junctions. + Nature Communications, 9(1), 5–10. + https://doi.org/10.1038/s41467-018-03003-7 + """ + # vector u_anisotropy == unitary z + # # uniaxial aniotropy constant in J/m^3 + # self.k_u = self.thermal_stability * self.temperature * \ + # c_KB / (self._volume) + # print(f'\t[debug] k_u: {self.k_u}') + # print(f'\t[debug] thermal stability: {self.thermal_stability}') + # return np.array([0., + # 0., + # 2 * self.k_u * m_cart[2] / (c_U0 * self.ms) + # ]) + return np.array([0., + 0., + 2 * self.k_i * m_cart[2] / + (self.t_fl * c_U0 * self.ms) + ]) + + def get_h_vcma(self, v_mtj): + """Get VCMA vector. + + Zhang, K., et. al. + Compact Modeling and Analysis of Voltage-Gated Spin-Orbit + Torque Magnetic Tunnel Junction. + IEEE Access, 8, 50792–50800. + https://doi.org/10.1109/ACCESS.2020.2980073 + """ + # WORKAROUND to validate vcma, see oommf validation + # return np.array([0., + # 0., + # -2 * self.xi * self.i_amp*self.r_p / ( + # self.t_fl * self.t_ox * c_U0 * self.ms) + # ]) + if not self.do_vcma: + return np.zeros(3) + return np.array([0., + 0., + -2 * self.xi * v_mtj / ( + self.t_fl * self.t_ox * c_U0 * self.ms) + ]) + + ####################### + # conductance + ####################### + def get_r(self, m_cart): + """Get conductance. + + Julliere, M. (1975). + Tunneling between ferromagnetic films. + Physics Letters A, 54(3), 225–226. + https://doi.org/10.1016/0375-9601(75)90174-7 + + Lee, H., et.al. + Analysis and Compact Modeling of Magnetic Tunnel Junctions Utilizing + Voltage-Controlled Magnetic Anisotropy. + IEEE Transactions on Magnetics, 54(4). + https://doi.org/10.1109/TMAG.2017.2788010 + """ + return self.r_p*(1 + self.tmr/(self.tmr + 2))/( + 1 - self.tmr/(self.tmr + 2)*m_cart[2]) + + ####################### + # differential vectors + ####################### + + def get_diff_unit_theta_vector(self, m_sph): + """Compute dt given infinitesimal displacement from ro, theta, phi.""" + return np.array([np.cos(m_sph[1])*np.cos(m_sph[2]), + np.cos(m_sph[1])*np.sin(m_sph[2]), + -np.sin(m_sph[1]) + ]) + + def get_diff_unit_phi_vector(self, m_sph): + """Compute dp given infinitesimal displacement from ro, theta, phi.""" + return np.array([-np.sin(m_sph[2]), + np.cos(m_sph[2]), + 0 + ]) + + def _g_cart(self, m_cart, v_cart): + """ + Compute g(mt, _v) wienner process. + + g is also commonly called b (under a b nomenclature). + """ + mxv = np.cross(m_cart, v_cart) + mxmxv = np.cross(m_cart, mxv) + + return self._U0_gamma_0_alpha * (-mxv + self.alpha*(-mxmxv)) + + def _g_sph(self, m_sph, v_cart): + """ + Compute g(mt, _v) wienner process. + + g is also commonly called b (under a b nomenclature). + """ + # m_cart = spherical_from_cart_np(m_sph) + # mxv = np.cross(m_cart, v_cart) + # mxmxv = np.cross(m_cart, mxv) + + # h_eff_phi (local orthogonal unit vector in the direction + # of increasing phi), etc + # differential terms from cart to spherical + diff_unit_theta_vector = self.get_diff_unit_theta_vector(m_sph) + diff_unit_phi_vector = self.get_diff_unit_phi_vector(m_sph) + d_h_th_theta = np.dot(v_cart, diff_unit_theta_vector) + d_h_th_phi = np.dot(v_cart, diff_unit_phi_vector) + + # as specified in OOMMF, gamma([(rad)/s/T]) should interface + # the fields in A/m, so introducing u0 + d_theta = self._U0_gamma_0_alpha * ( + d_h_th_phi + self.alpha*(d_h_th_theta)) + d_phi = self._U0_gamma_0_alpha / np.sin(m_sph[1]) * ( + -d_h_th_theta + self.alpha*d_h_th_phi) + + # dmdt is [dro, dthetadt, dphidt] + return np.asarray([0, d_theta, d_phi], dtype=np.float64) + + def _f_sph(self, t, m_sph, dt=np.inf): + """ + Compute dm_sph/dt fn. + + LLG equation (with secondary stt term) at eq 1.3 + Switching distributions for perpendicular spin-torque devices + within the macrospin approximation. + IEEE Transactions on Magnetics, 48(12), 4684–4700. + https://doi.org/10.1109/TMAG.2012.2209122 + see OOMMF for the value of the different terms + and + Ament, S., et al. + Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski + equation for monodomain nanomagnets : + A survey and analysis of numerical techniques. + 1–19. Retrieved from http://arxiv.org/abs/1607.04596 + """ + # check integrity + if np.isnan(t): + print('[ERROR] time is NaN') + raise Exception + # update variables + _t = t + _dt = dt + # reverse time normalization + # as i_amp is in natural time [s] + if self.solve_normalized: + _t /= self.ms * c_U0 * c_gamma_0 + _dt /= self.ms * c_U0 * c_gamma_0 + + # m, Is, mxp and mxpxm vectors + m_cart = cart_from_spherical_fn(m_sph) + + # Fn defining the current + i_amp = self.i_amp_fn(_t) + r = self.get_r(m_cart) + v_mtj = i_amp * r + + # m*p, mxp and mxpxm + # note that self.i_amp is signed, and that Is is in Z axis + mdp = np.dot(m_cart, self.p_cart) + # not used as the terms are directly computed in spherical coord. + # mxp = uxv_cart(m_cart, self.p_cart) + # mxpxm = uxv_cart(mxp, m_cart) + + # add extra additional terms to the effective field: + # different anisotropy terms, demag, etc + h_eff_cart = np.array(self.h_ext_cart(_t)) + h_eff_cart += self.get_h_demag(m_cart) + h_eff_cart += self.get_h_exchange() + h_eff_cart += self.get_h_una(m_cart) + h_eff_cart += self.get_h_vcma(v_mtj) + + # wienner process + # similar to what SPICE solvers do + if self.do_thermal and _dt != np.inf: + h_eff_cart += self.get_h_th(_dt) + + # stt component + epsilon = self.get_epsilon_stt(mdp) + # beta computation as in OOMMF + # beta units are [A/m] + beta = i_amp * self._stt_scale_fac + + # do normalized sLLGS + if self.solve_normalized: + beta /= self.ms + h_eff_cart /= self.ms + self._U0_gamma_0_alpha = 1 + + # for oomffc validation + # self.ut = self.beta * self.epsilon + # both eps and eps prime contributions + # h_stt components in [A/m] + h_stt_1_cart = beta * (epsilon * self.p_cart) + h_stt_2_cart = beta * (self.eps_prime * self.p_cart) + + # h_eff_phi (local orthogonal unit vector in the direction + # of increasing phi), etc + # differential terms from cart to spherical + diff_unit_theta_vector = self.get_diff_unit_theta_vector(m_sph) + diff_unit_phi_vector = self.get_diff_unit_phi_vector(m_sph) + d_h_eff_theta = np.dot(h_eff_cart, diff_unit_theta_vector) + d_h_eff_phi = np.dot(h_eff_cart, diff_unit_phi_vector) + d_h_stt_1_theta = np.dot(h_stt_1_cart, diff_unit_theta_vector) + d_h_stt_1_phi = np.dot(h_stt_1_cart, diff_unit_phi_vector) + d_h_stt_2_theta = np.dot(h_stt_2_cart, diff_unit_theta_vector) + d_h_stt_2_phi = np.dot(h_stt_2_cart, diff_unit_phi_vector) + + # as specified in OOMMF, gamma([(rad)/s/T]) should interface + # the fields in A/m, so introducing u0 + # OOMMF like, not normalized + d_theta = self._U0_gamma_0_alpha * ( + d_h_eff_phi + d_h_stt_1_theta - d_h_stt_2_phi + + self.alpha*( + d_h_eff_theta - d_h_stt_1_phi - d_h_stt_2_theta)) + d_phi = self._U0_gamma_0_alpha / np.sin(m_sph[1]) * ( + -d_h_eff_theta + d_h_stt_1_phi + d_h_stt_2_theta + + self.alpha*( + d_h_eff_phi + d_h_stt_1_theta - d_h_stt_2_phi)) + + # fake theta term + if (not self.do_thermal) and self.do_fake_thermal: + # extra term can be seen as a direct contribution to h_phi + # as d_theta/dt dependence on h_th is + # ~ h_th_phi + alpha * h_th_theta + t_th = self._U0_gamma_0_alpha * \ + self.th_power_noise_std / np.sqrt(_dt) + if self.solve_normalized: + t_th /= self.ms + contrib = -self.d_theta_fake_th*np.sign(m_sph[1]-np.pi/2)*t_th + d_theta += contrib + # saturate d_theta to emulate min field generated + # by the thermal noise, avoiding total damping + if (not self.do_thermal) and self.do_theta_windowing and dt > 0: + d_theta *= self.theta_windowing(d_theta=d_theta, + theta=m_sph[1], + theta_min=self.theta_min) + + # dmdt is [dro, dthetadt, dphidt] + return np.asarray([0, d_theta, d_phi], dtype=np.float64) + + def _f_cart(self, t, m_cart, dt=np.inf): + """ + Compute dm_cart/dt fn. + + Targets the deterministic component for Heun method. + LLG equation (with secondary stt term) at eq 1.3 + Switching distributions for perpendicular spin-torque devices + within the macrospin approximation. + IEEE Transactions on Magnetics, 48(12), 4684–4700. + https://doi.org/10.1109/TMAG.2012.2209122 + see OOMMF for the value of the different terms + and + Ament, S., et al. + Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski + equation for monodomain nanomagnets : + A survey and analysis of numerical techniques. + 1–19. Retrieved from http://arxiv.org/abs/1607.04596 + """ + # check integrity + if np.isnan(t): + print('[ERROR] time is NaN') + raise Exception + # update variables + _t = t + _dt = dt + # reverse time normalization + # as i_amp is in natural time [s] + if self.solve_normalized: + _t /= self.ms * c_U0 * c_gamma_0 + _dt /= self.ms * c_U0 * c_gamma_0 + + # Fn defining the current + i_amp = self.i_amp_fn(_t) + r = self.get_r(m_cart) + v_mtj = i_amp * r + + # m*p, mxp and mxpxm + # note that self.i_amp is signed, and that Is is in Z axis + mdp = np.dot(m_cart, self.p_cart) + mxp = np.cross(m_cart, self.p_cart) + mxmxp = np.cross(m_cart, mxp) + + # not used as the terms are directly computed in spherical coord. + # mxp = uxv_cart(m_cart, self.p_cart) + # mxpxm = uxv_cart(mxp, m_cart) + + # add extra additional terms to the effective field: + # different anisotropy terms, demag, etc + h_eff_cart = np.array(self.h_ext_cart(_t)) + h_eff_cart += self.get_h_demag(m_cart) + h_eff_cart += self.get_h_exchange() + h_eff_cart += self.get_h_una(m_cart) + h_eff_cart += self.get_h_vcma(v_mtj) + + # wienner process + # similar to what SPICE solvers do + if self.do_thermal and _dt != np.inf: + h_eff_cart += self.get_h_th(_dt) + + # stt component + # epsilon = self.get_epsilon_stt(mdp) + # beta computation as in OOMMF + # beta units are [A/m] + beta = i_amp * self._stt_scale_fac + + # do normalized sLLGS + if self.solve_normalized: + beta /= self.ms + h_eff_cart /= self.ms + self._U0_gamma_0_alpha = 1 + + # for oomffc validation + # self.ut = self.beta * self.epsilon + # both eps and eps prime contributions + # h_stt components in [A/m] + h_stt_1 = beta * self.get_epsilon_stt(mdp) + h_stt_2 = beta * self.eps_prime + + # compute cross products + mxh_eff = np.cross(m_cart, h_eff_cart) + mxmxh_eff = np.cross(m_cart, mxh_eff) + mxp_h_stt_1 = h_stt_1 * mxp + mxp_h_stt_2 = h_stt_2 * mxp + mxmxp_h_stt_1 = h_stt_1 * mxmxp + mxmxp_h_stt_2 = h_stt_2 * mxmxp + + return self._U0_gamma_0_alpha * ( + -mxh_eff - mxmxp_h_stt_1 + mxp_h_stt_2 + + self.alpha*(-mxmxh_eff + mxp_h_stt_1 + mxmxp_h_stt_2)) + + def solve_ode(self, + final_t, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=False, + method='RK45', + # preferred method to control accuracy: + rtol=1e-3, + atol=1e-6, + # other non-preferred way: + max_step=np.inf): + """ + Integrate a dm_sph/dt fn. + + ####################### + Non stochastic methods: + ####################### + a) Scipy solve_ivp (RK45, ...) by using "scipy_ivp=True" + b) Other methods by using "scipy_ivp=True" + Horley, P., et. al. (2011). + Numerical Simulations of Nano-Scale Magnetization Dynamics. + Numerical Simulations of Physical and Engineering Processes. + https://doi.org/10.5772/23745 + + ##################### + SDE methods: + ##################### + a) Horley, P., et. al. (2011). + Numerical Simulations of Nano-Scale Magnetization Dynamics. + Numerical Simulations of Physical and Engineering Processes. + https://doi.org/10.5772/23745 + b) Ament, S., Rangarajan, N., Parthasarathy, A., & Rakheja, S. (2016). + Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski equation + for monodomain nanomagnets : A survey and analysis + of numerical techniques. 1–19. + http://arxiv.org/abs/1607.04596 + ##################### + """ + # solved normalized sLLGS or OOMMF Oxs_SpinXferEvolve + self.solve_normalized = solve_normalized + + # max_tstep_saved + if max_step < 1e-12: + saved_max_step = 1e-12 + else: + saved_max_step = max_step + + # max_step/tstep checks + if not scipy_ivp and max_step is None or max_step == np.inf: + print('[error] Max step should be specified when not using ' + 'scipy_ivp mode') + return None + if not scipy_ivp and not solve_spherical and max_step > 1e-13: + print(f'[warning] max_step {max_step} might ' + 'not be sufficiently small, use <=0.5e-13') + elif not scipy_ivp and solve_spherical and max_step > 1e-12: + print(f'[warning] max_step {max_step} might ' + 'not be sufficiently small, use <=1e-12') + method_info = method + if solve_spherical: + _f = self._f_sph + _g = self._g_sph + y0 = self.m_sph_init + method_info += ', spherical coordinates' + else: + _f = self._f_cart + _g = self._g_cart + y0 = self.m_cart_init + method_info = ', cartesian coordinates' + + save_every = int(saved_max_step/max_step) + # normalization + # do time normalization + if self.solve_normalized: + final_t *= self.ms * c_U0 * c_gamma_0 + max_step *= self.ms * c_U0 * c_gamma_0 + saved_max_step *= self.ms * c_U0 * c_gamma_0 + self._U0_gamma_0_alpha = 1. + method_info += ', normalized time' + else: + self._U0_gamma_0_alpha = c_U0 * \ + c_gamma_0 / (1 + self.alpha*self.alpha) + method_info += ', not time normalized' + + if (self.do_thermal or self.do_fake_thermal) and scipy_ivp: + print('[info] Calling custom scipy fn for solver...') + # Application of the monkeypatch to replace rk_step + # with the custom behavior (expose h to _f) + base.check_arguments = custom_ode.check_arguments_custom + base.OdeSolver.__init__ = custom_ode.ode_solver_init_custom + rk.rk_step = custom_ode.rk_step_custom + + # solve ode using RK from scipy (altered solver if noise present) + # Default under no Wienner process + # can be forced to match SPICE like solvers + if scipy_ivp: + print(f'[info][solver] solve_ivp. Method: {method_info}') + print(f'[info] rtol: {rtol} atol: {atol} max_step:{max_step}') + if self.do_thermal: + print('[warning][solver] You are using scipy ivp solver.' + 'For the stochastic simulations, ' + 'the use of a SDE solver is encouraged. ' + 'See "scipy_ivp" parameter.') + + # time normalization already done + sol = solve_ivp(fun=_f, + # lambda t, + # y: _f(t, y, a), + t_span=[0, final_t], + # t_eval=np.linspace(0, final_t, 10000), + y0=y0, + method=method, + # method='RK23' + # method='RK45' + # method='DOP853' + # ... + # preferred method to control accuracy: + rtol=rtol, + atol=atol, + # other (non-preferred) way + max_step=max_step, + ) + # restart variables + self.t_i_idx = 0 + # y0 is restarted when solve_ivp is called + # renormalize time + if self.solve_normalized: + sol.t /= self.ms * c_U0 * c_gamma_0 + # return solution + return sol + + print(f'[info][solver] Integration Method: {method_info}') + print(f'[info][solver] max_step (dt):{max_step}') + + # time normalization already done + n_t = int(final_t/max_step) + dt = max_step + # saved vals + t_eval = np.linspace(0, final_t, int(final_t/saved_max_step)) + m_eval = np.zeros((t_eval.shape[0], 3)) + # computed_vals + sqrt_dt = np.sqrt(dt) + m_eval[0] = y0 + _m = y0 + _m_next = _m + t = 0 + t_idx = 0 + save_idx = 0 + + th_gamma = c_gamma_0 * c_U0 / (1 + self.alpha*self.alpha) + _sig = np.sqrt( + 2. * self.alpha * self.temperature * c_KB / + (c_U0 * th_gamma * self.ms * self._volume)) + v_cart = np.array([_sig, _sig, _sig]) + + # different options + while t_idx < n_t: + _dW = self.rng.normal(loc=0, scale=1, size=(3)) * sqrt_dt + if method == 'naive_euler': + _m_next = _m + dt * _f(t, _m) + elif method == 'heun': + _dm = _f(t, _m) + _m_prime = _m + dt * _dm + if not solve_spherical: + _m_prime = normalize_cart(_m_prime) + _m_next = _m + 0.5*dt*(_dm + _f(t, _m_prime)) + elif method == 'rk45': + if solve_spherical: + _f1 = _f(t, _m) + _f2 = _f(t + dt / 2.0, _m + dt * _f1 / 2.0) + _f3 = _f(t + dt / 2.0, _m + dt * _f2 / 2.0) + _f4 = _f(t + dt, _m + dt * _f3) + else: + _f1 = _f(t, _m) + _m1 = normalize_cart(_m + 0.5*dt * _f1) + _f2 = _f(t + 0.5*dt, _m1) + _m2 = normalize_cart(_m + 0.5*dt * _f2) + _f3 = _f(t + 0.5*dt, _m2) + _m3 = normalize_cart(_m + dt * _f3) + _f4 = _f(t + dt, _m3) + _m_next = _m + dt * \ + (_f1 + 2.0 * _f2 + 2.0 * _f3 + _f4) / 6.0 + elif method == 'stratonovich_heun': + # Stratonovich Heun's + _dm = _f(t, _m, dt=np.inf) + _dg = _g(_m, v_cart=v_cart) + _m_prime = _m + dt * _dm + _dg * _dW + if not solve_spherical: + _m_prime = normalize_cart(_m_prime) + _m_next = _m + \ + 0.5 * dt * (_dm + _f(t, _m_prime, dt=np.inf)) +\ + 0.5 * _dW * (_dg + _g(_m_prime, v_cart=v_cart)) + elif method == 'stratonovich_rk_weak_2': + # RK 2 Stratonovich + _dm = _f(t, _m, dt=np.inf) + _dg = _g(_m, v_cart=v_cart) + _m_prime = _m + 2/3*(dt * _dm + _dg * _dW) + if not solve_spherical: + _m_prime = normalize_cart(_m_prime) + _m_next = _m + dt*(0.25*_dm + 0.75*_f(2/3*dt + t, _m_prime)) \ + + _dW*(0.25*_dg + 0.75*_g(_m_prime, v_cart=v_cart)) + else: + print(f'[error] method "{method}" not recogniced ' + 'for the custom solver. ' + 'Use "naive_euler", "heun", "rk45",' + '"stratonovich_heun", "stratonovich_rk_weak_2"') + return None + # cartesians require normalization + if not solve_spherical: + _m_next = normalize_cart(_m_next) + # update vars + _m = _m_next + t += dt + t_idx += 1 + # save + if (t_idx % save_every) == 0: + m_eval[save_idx] = _m + save_idx += 1 + + if not solve_spherical: + # conversion + m_eval = spherical_from_cart_np(m_eval) + # reverse time normalization + if self.solve_normalized: + t_eval /= self.ms * c_U0 * c_gamma_0 + sol = Sol() + sol.t = t_eval + sol.y = m_eval.T + return sol + + def state_plotter(self, + times, + states, + h_ext_cart, + i_amp, + theta_0, + title, + plot_xy=True, + plot_simple=False): + """Plot magnetic state/evolution.""" + if plot_simple: + # \t[debug] + fig = plt.figure(figsize=plt.figaspect(1.)) + xyz = cart_from_spherical_fn(states.T) + ax_2d = fig.add_subplot(1, 1, 1) + if plot_xy: + ax_2d.plot(1e9*times, xyz[:, 0], ':', label='x') + ax_2d.plot(1e9*times, xyz[:, 1], ':', label='y') + ax_2d.plot(1e9*times, xyz[:, 2], label='z') + ax_2d.set_ylabel('m/|m|') + ax_2d.set_xlabel('time (ns)') + ax_2d.legend() + ax_2d.set_ylim([-1, 1]) + ax_2d.grid() + plt.title(title) + plt.show() + return + + # states = states.T + # states = cart_from_spherical_fn(states) + # states = spherical_from_cart_np(states) + # states = states.T + + fig = plt.figure(figsize=plt.figaspect(3.)) + # Plot h_ext_z and i_amp. + # Recalculating these just to plot, which is a bit of duplication. + # But avoids touching the solver code and ensures identical + # timestamps to solver. + # Not using append() because it's very inefficient in numpy. + h_ext_pts = np.zeros(len(times)) + i_amp_pts = np.zeros(len(times)) + for i in range(len(times)): + h_ext_pts[i] = h_ext_cart(times[i])[2] + i_amp_pts[i] = i_amp(times[i]) + ax_h = fig.add_subplot(2, 2, 1) + ax_h.plot(1e9*times, h_ext_pts) + ax_h.set_ylabel('h_ext_z (A/m)') + ax_h.grid() + ax_i = fig.add_subplot(2, 2, 2) + ax_i.plot(1e9*times, 1e6*i_amp_pts) + ax_i.set_ylabel('i_amp (uA)') + ax_i.grid() + + ax_theta = fig.add_subplot(2, 2, 3) + ax_theta.plot(1e9*times, states[1], label='theta') + ax_theta.plot(1e9*times, np.abs(states[1]), label='abs_theta') + ax_theta.plot(1e9*times, theta_0*np.ones(times.shape), label='theta_0') + # non-uniform time axis, integrate and divide + if times[-1] != times[0]: + theta_mean = np.trapz( + np.abs(states[1]), times)/(times[-1] - times[0]) + print(f'\t[debug] mean theta: {theta_mean}') + ax_theta.plot(1e9*times, theta_mean*np.ones(times.shape), + label='theta_mean') + ax_theta.set_ylabel('theta [rad]') + ax_theta.legend() + ax_theta.grid() + + # Data for three-dimensional scattered points + xyz = cart_from_spherical_fn(states.T) + ax_2d = fig.add_subplot(2, 2, 4) + if plot_xy: + ax_2d.plot(1e9*times, xyz[:, 0], ':', label='x') + ax_2d.plot(1e9*times, xyz[:, 1], ':', label='y') + ax_2d.plot(1e9*times, xyz[:, 2], label='z') + ax_2d.set_ylabel('m/|m|') + ax_2d.set_xlabel('time (ns)') + ax_2d.legend() + ax_2d.set_ylim([-1, 1]) + ax_2d.grid() + + # Now plot polar 3D plot + fig3d = plt.figure(figsize=plt.figaspect(1.)) + ax_3d = fig3d.add_subplot(1, 1, 1, projection='3d') + ax_3d.plot3D(xyz[:, 0], xyz[:, 1], xyz[:, 2]) + ax_3d.scatter3D(xyz[:, 0], xyz[:, 1], xyz[:, 2], + c=times, cmap='Greens', alpha=0.2) + ax_3d.set_xlim([-1, 1]) + ax_3d.set_ylim([-1, 1]) + ax_3d.set_zlim([-1, 1]) + ax_3d.set(xlabel='X', + ylabel='Y', + zlabel='Z') + plt.title(title) + plt.show() + + def solve_and_plot(self, + final_t=40e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=False, + method='RK45', + # preferred method to control accuracy: + rtol=1e-3, + atol=1e-6, + # other method (non-preferred) + max_step=np.inf, + plot=True, + plot_simple=True): + """Solve and plot ODE.""" + t_start = time.time() + ode = self.solve_ode(final_t, + scipy_ivp=scipy_ivp, + solve_spherical=solve_spherical, + solve_normalized=solve_normalized, + method=method, + rtol=rtol, + atol=atol, + max_step=max_step, + ) + if ode is None: + print('[error] an error occurred while computing the ode') + return + t_end = time.time() + print(f'[info] solver took {t_end-t_start} [s]') + title = f'{final_t*1e9} ns. Solver: {method}, ' + if solve_spherical: + title += ' spherical coords, ' + else: + title += ' cartesian coords, ' + if scipy_ivp: + title += ' Scipy solver, ' + else: + title += ' custom solver, ' + title += f'max_step: {max_step} s' + if plot: + self.state_plotter(title=title, + times=ode.t, + states=ode.y, + h_ext_cart=self.h_ext_cart, + i_amp=self.i_amp_fn, + theta_0=self.theta_0, + plot_simple=plot_simple) + return ode diff --git a/src/python_compact_model/stochastic_multithread_simulation.py b/src/python_compact_model/stochastic_multithread_simulation.py new file mode 100644 index 0000000..ff099ab --- /dev/null +++ b/src/python_compact_model/stochastic_multithread_simulation.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +Multi-threading capabilities example. + +Generation of 'total_th_sims' random walks for later comparison against FP. +""" + +import numpy as np +import argparse +import matplotlib.pyplot as plt +import multiprocessing + +import sllgs_solver as sllg_solver + + +################################# +################################# +# Parameters not passed to the worker, +# change accordingly to your needs +cores = 32 +total_th_sims = 10000 + +c_KB = 1.3806503e-23 # boltzmann's constant [J/K] +diam = 50e-9 +t_fl = 1.e-9 +temperature = 300 +k_i_0 = 1.0e-3 +ms = 1.2e6 # magnetisation saturation (A/m) +H_ext = (0., 0., 0.) # low external magnetic field (A/m) (test anisotropy) +# stt params +eps_prime = 0.0 # [FITTING] constant +p = 0.75 +lambda_s = 1. +z0 = 1 +initial_m = None + +# just for export +t_res = 1e-11 +################################# +################################# + + +def str2bool(b_string): + """Convert string to bool.""" + b_string = b_string.strip() + if b_string.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif b_string.lower() in ('no', 'false', 'f', 'n', '0'): + return False + # error + print('[ERROR] Boolean value expected.') + return 'a' + + +def worker_stt_simplest(idx, + solve_normalized, + method, + I0, + t_delay, + t_pulse, + t_final, + H_ext, + initial_m, + w_fl, + l_fl, + t_fl, + ms, + alpha, + k_i_0, + temperature, + p, + lambda_s, + eps_prime, + do_thermal, + do_theta_windowing, + do_fake_thermal, + theta_windowing_factor, + d_theta_fake_th, + max_step): + """Parallel worker fn. A simple wrapper.""" + # current + I0 = -np.sign(z0)*I0 + + def test_h_ext(t): + """H_ext fn.""" + return np.array(H_ext) + + def test_current(t): + """Current fn.""" + if type(t) is np.ndarray: + c = np.zeros(t.shape) + I0 + c[t < t_delay] = 0 + c[t > t_delay+t_pulse] = -I0 + return c + if t < t_delay: + return 0 + elif t > t_delay+t_pulse: + return 0 + return I0 + + print(f'[info][parallel_solver] doing s: {idx} I0 {I0} alpha {alpha} ' + f'max_step {max_step} tf {t_final}') + llg_b = sllg_solver.LLG( + w_fl=diam, + l_fl=diam, + t_fl=t_fl, + ms=ms, + alpha=alpha, + k_i_0=k_i_0, + temperature=temperature, + stt_mode='stt_oommf_simple', + p=p, + lambda_s=lambda_s, + eps_prime=eps_prime, + # not include temperature noise, + # and do not force its effects on the theta_0 + do_thermal=do_thermal, + do_theta_windowing=do_theta_windowing, + do_fake_thermal=do_fake_thermal, + theta_windowing_factor=theta_windowing_factor, + d_theta_fake_th=d_theta_fake_th, + m_init=initial_m, + theta_pl=0.0, # [rad] pinned layer theta + phi_pl=0.0, # [rad] pinned layer phi + h_ext_cart=test_h_ext, + i_amp_fn=test_current, + seed=idx+2000) + llg_sol = llg_b.solve_and_plot(final_t=t_final, + max_step=max_step, + solve_spherical=False, + solve_normalized=solve_normalized, + scipy_ivp=False, + method=method, + plot=False, + plot_simple=False) + print(f'[info][parallel_solver] done {idx} I0 {I0}' + f' alpha: {alpha} t_final: {t_final}') + + # do interpolation in the thread + t_i = np.linspace(0, t_final, int(t_final/t_res)) + theta_i = np.interp(t_i, llg_sol.t, llg_sol.y[1]) + + # do interpolation in the thread + return theta_i + + +def run_walks( + suffix, + solve_normalized, + method, + I0, + alpha, + max_step, + t_delay, + t_pulse=60e-9, + total_th_sims=10000, + cores=32, + plot_walks=True, +): + """Run walks.""" + t_final = 2*t_delay + t_pulse + + # pool + pool = multiprocessing.Pool(cores) + results_a = [pool.apply_async( + worker_stt_simplest, + args=( + idx, + solve_normalized, + method, + I0, + t_delay, + t_pulse, + t_final, + H_ext, + initial_m, + diam, # w_fl, + diam, # l_fl, + t_fl, + ms, + alpha, + k_i_0, + temperature, + p, + lambda_s, + eps_prime, + True, # do_thermal, + False, # do_theta_windowing, + False, # do_fake_thermal, + 0., # theta_windowing_factor, + 1/13, # d_theta_fake_th + max_step, + )) for idx in range(total_th_sims)] + print('[info][parallel_solver] all threads launched') + + theta_i_list = [r.get() for r in results_a] + t_i = np.linspace(0, t_final, int(t_final/t_res)) + print('[info][parallel_solver] all threads computed') + + np.savetxt( + f'{suffix}_{t_delay}_del_{I0}A_interp_{total_th_sims}_sims.csv', + np.array(theta_i_list)) + np.savetxt( + f'{suffix}_{t_delay}_del_{I0}A_time_interp_' + f'{total_th_sims}_sims.csv', + t_i) + + if not plot_walks: + return + + print('[info][parallel_solver] plotting') + fig, ax = plt.subplots(1, 1, figsize=(6, 2.3)) + + for i in np.arange(total_th_sims): + ax.plot(1e9*t_i, np.cos(theta_i_list[i]), 'gray', alpha=0.5) + + ax.plot(np.nan, + color='gray', alpha=0.5, label='Stochastic H_th') + ax.set_yticks([-1, -0.5, 0, 0.5, 1]) + ax.set_ylabel('m_z') + ax.set_xlabel('time [ns]') + ax.legend(loc='upper right') + ax.grid() + ax.set_title(suffix) + plt.show() + plt.savefig(f'{suffix}_{t_delay}_del_{I0}A_time_interp.png') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + parser.add_argument( + '--solve-normalized', + type=str2bool, + default=False, + help='use normalized solver') + parser.add_argument( + '--method', + type=str, + default='heun', + help='sde solver') + parser.add_argument( + '--suffix', + type=str, + default='rand_walk', + help='suffix') + parser.add_argument( + '--max-step', + type=float, + default=1e-13, + help='max step [s]') + parser.add_argument( + '--I0', + type=float, + default=40e-6, + help='wr current') + parser.add_argument( + '--alpha', + type=float, + default=0.01, + help='damping') + parser.add_argument( + '--t-delay', + type=float, + default=3e-9, + help='delay time') + parser.add_argument( + '--t-pulse', + type=float, + default=60e-9, + help='sim time') + parser.add_argument( + '--total-th-sims', + type=int, + default=int(1e2), + help='sims') + parser.add_argument( + '--cores', + type=int, + default=int(32), + help='cores') + parser.add_argument( + '--plot-walks', + type=str2bool, + default=False, + help='Plot distribution') + + args = parser.parse_args() + + # # parse and run + # FLAGS, unparsed = parser.parse_known_args() + print('\t[experiment] reproduce results with:') + for k in args.__dict__: + print('\t\t', k, ': ', args.__dict__[k]) + + run_walks( + solve_normalized=args.solve_normalized, + method=args.method, + suffix=args.suffix, + max_step=args.max_step, + I0=args.I0, + alpha=args.alpha, + t_delay=args.t_delay, + t_pulse=args.t_pulse, + total_th_sims=args.total_th_sims, + cores=args.cores, + plot_walks=args.plot_walks + ) + + print('\t[experiment] reproduce results with:') + for k in args.__dict__: + print('\t\t', k, ': ', args.__dict__[k]) diff --git a/src/python_compact_model/test_sllgs_solver.py b/src/python_compact_model/test_sllgs_solver.py new file mode 100644 index 0000000..111879a --- /dev/null +++ b/src/python_compact_model/test_sllgs_solver.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""s-LLGS basic examples""" + +import sllgs_solver as sllg_solver + + +def current(t): + """Current waveform.""" + return -100e-6 + + +def do_test(seed_0=100): + """Simple s-LLGS tests.""" + sllg_solver.eq_info() + # No thermal, no fake_thermal, solved with scipy_ivp + llg_a = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_a = llg_a.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + max_step=1e-11, + rtol=1e-4, + atol=1e-9) + + # No thermal, fake_thermal, solved with scipy_ivp + llg_b = sllg_solver.LLG(do_fake_thermal=True, + d_theta_fake_th=1/30, + do_thermal=False, + i_amp_fn=current, + seed=seed_0) + data_b = llg_b.solve_and_plot(15e-9, + scipy_ivp=True, + solve_spherical=True, + solve_normalized=True, + max_step=1e-11, + rtol=1e-4, + atol=1e-9) + + llg_c = sllg_solver.LLG(do_fake_thermal=False, + do_thermal=True, + i_amp_fn=current, + seed=seed_0) + data_c = llg_c.solve_and_plot(10e-9, + scipy_ivp=False, + solve_spherical=False, + solve_normalized=True, + max_step=1e-13, + method='stratonovich_heun') + print(f'data ready to inspect: {data_a}, {data_b}, {data_c}') + + +do_test() diff --git a/src/sllgs_fpe_comparison/plot_sllgs_fpe_comparison.py b/src/sllgs_fpe_comparison/plot_sllgs_fpe_comparison.py new file mode 100644 index 0000000..48278e9 --- /dev/null +++ b/src/sllgs_fpe_comparison/plot_sllgs_fpe_comparison.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Plot s-LLGS/FPE evolution. + +Please, setup all the required parameters. +Inputs are the files generated by stochastic_multithread_simulation.py +""" + +import numpy as np +import matplotlib.pyplot as plt +import itertools +import matplotlib.colors as mcolors + +import fooker_plank.analytical as analytical +import python_compact_model.sllgs_solver as sllgs_solver +import sllgs_importer as sllgs_i + +###################################################################### +# PARAMS for building llgs object +# these should match the ones you used for the s-LLGS simulation +###################################################################### +# Define a macrospin mesh (i.e. one discretisation cell). +z0 = 1 +initial_m = None # np.array([0.01, 0.01, z0]) # vector in x direction +diam = 50e-9 +t_fl = 1.e-9 +temperature = 300 +k_i_0 = 1.0e-3 +alpha = 0.01 # Gilbert damping +Ms = 1.2e6 # magnetisation saturation (A/m) +# Zeeman (external field) validation +Hext = (0., 0., 0.) # low external magnetic field (A/m) (test anisotropy) +# stt params +eps_prime = 0.0 # [FITTING] constant +P = 0.75 +Lambda = 1. + +################################### +sllgs_files = [ + 'path_to_csv_file_exported_by_stochastic_multithread_simulation.py', +] +sllgs_time_files = [ + 'path_to_time_csv_file_exported_by_stochastic_multithread_simulation.py', +] +I0_uA = [50]*len(sllgs_files) +t_delays_ns = [5]*len(sllgs_files) + +ps_sslgs = [None] * len(t_delays_ns) +time_sslgs = [None] * len(t_delays_ns) +ps_fp = [None] * len(t_delays_ns) +time_fp = [None] * len(t_delays_ns) +compute_analytical_manual = False +plot_intermediate = False +colors = itertools.cycle(mcolors.TABLEAU_COLORS) + + +def test_h_ext(t): + """Set H_ext.""" + return np.array(Hext) + + +def test_current(t): + """Test current.""" + return 0. + + +# get Ic, tau_d etc +llg_o = sllgs_solver.LLG(w_fl=diam, l_fl=diam, t_fl=t_fl, + ms=Ms, + alpha=alpha, + k_i_0=k_i_0, + # thermal_stability_0=thermal_stability_0, + temperature=temperature, + stt_mode='stt_oommf_simple', + p=P, + lambda_s=Lambda, + eps_prime=eps_prime, + # not include temperature noise, + # and do not force its effects on the theta_0 + do_thermal=False, + do_fake_thermal=False, + do_theta_windowing=False, + m_init=initial_m, + # shape_ani_demag_mode=0, + # theta_init=0.09, + theta_pl=0.0, # [rad] pinned layer theta + phi_pl=0.0, # [rad] pinned layer phi + h_ext_cart=test_h_ext, + i_amp_fn=test_current) + +print(f'P: {P}, P/2: {P/2}, eps: {llg_o.get_epsilon_stt(1)}') + +############################################################################## +# importing raw data +# ~/SharePoint/ProjectEnergyScalableMRAM/docs/essderc_2021/raw_data +############################################################################## + + +def analyze_s_llgs_tolerances(data_file, time_file, t_delay, I0_uA, + compute_analytical_manual=False, + plot_intermediate=True, + dim_points=1000): + """Analyze s-LLGS.""" + # get all data, note it is not delayed anymore + sllgs_time, sllgs_data, sllgs_sw_idx, sllgs_sw_ps = sllgs_i.process_sllgs_data( + data_file, time_file, t_delay, dim_points) + theta_axis = np.linspace(np.pi, 0, dim_points) + + # debug sllgs + # return { + # 'sllgs_time': sllgs_time, + # 'sllgs_theta': theta_axis, + # 'sllgs_data': sllgs_data, + # 'sllgs_ps': sllgs_sw_ps + # } + ######################################################################## + # Initializations + I0 = np.sign(z0)*I0_uA*1e-6 + + lin_space_z = False + L0_max = 200 + Nmax = L0_max + + # maxwell_loc = -0.016632336229383336 + # maxwell_scale = 0.041628860303107654 + maxwell_loc = None + maxwell_scale = None + + # i, h, delta + # llg_o.ic = llg_o.ic * 1.1 + # llg_o.tau_d = llg_o.tau_d * 1.1 + print(f'Ic from: {llg_o._get_Ic()}') + ic = np.abs(llg_o._get_Ic()) + # ic = 4*llg.c_E * llg_o.alpha * llg_o.thermal_stability * \ + # llg.c_KB * temperature / (llg.c_hbar * llg_o.get_epsilon_stt(1)*2) + print(f'Ic from compact modeling: {ic}') + print(f'epsilon: {llg_o.get_epsilon_stt(1)}') + i = I0/ic + print(f'[debug] i: {i}') + h = 0 + delta = llg_o.thermal_stability + + # FP Data + xlim = sllgs_time[-1] + tau = xlim/np.abs(llg_o._get_tau_d()) + tau_step = 0.2e-9/np.abs(llg_o._get_tau_d()) + t_pulse = 60e-9 + print(f't_final: {xlim}') + print(f'tau: {tau} i: {i}') + lin_space_z = False + rho_0_at_pi = z0 < 0 + + s_rho_0_fit = np.polynomial.legendre.legfit( + x=np.cos(theta_axis), + # sllgs is normalized to 1 already + y=sllgs_data[:, 0][::-1], + deg=L0_max) + _, rho_0_a, s_rho_0_a = analytical.get_state_rho_0( + delta=delta, + do_maxwell=True, + maxwell_loc=maxwell_loc, + maxwell_scale=maxwell_scale, + L0=Nmax, + lin_space_z=lin_space_z, + rho_0_at_pi=rho_0_at_pi) + _, _, s_rho_0_b = analytical.get_state_rho_0(delta=delta, + do_maxwell=False, + L0=Nmax, + dim_points=dim_points, + lin_space_z=lin_space_z, + rho_0_at_pi=rho_0_at_pi) + + # fergar01 + # s_rho_0_fit = s_rho_0_b + + fp_theta, ddata_fit = analytical.untangle_state(s_rho_0_fit, + dim_points=dim_points, + lin_space_z=lin_space_z) + fp_theta, ddata_a = analytical.untangle_state(s_rho_0_a, + lin_space_z=lin_space_z, + dim_points=dim_points) + fp_theta, ddata_b = analytical.untangle_state(s_rho_0_b, + lin_space_z=lin_space_z, + dim_points=dim_points) + + fig, axs = plt.subplots(1, 1, + figsize=(8, 6)) + axs.plot(theta_axis, + sllgs_data[:, 0][::-1], + '+-', + label='sllgs') + axs.plot(fp_theta, + ddata_fit, + label='fit') + axs.plot(fp_theta, + ddata_a, + '--', + label='A') + axs.plot(fp_theta, + ddata_b, + '--', + label='B') + axs.grid() + axs.legend() + if plot_intermediate: + plt.show() + else: + plt.savefig(f'fit_{data_file.split("/")[-1]}.png') + + # use fitted + s_rho_0_a = s_rho_0_fit + # use default + # s_rho_0_a = s_rho_0_a + + sn = analytical.get_sn(Nmax) + fp_data_a = analytical.get_sw_continuous_prob( + s_rho_0=s_rho_0_a, + tau=tau, + delta=delta, + i=i, + h=h, + rho_0_at_pi=rho_0_at_pi, + compute_fvm=False, + compute_analytical_sw=True, + compute_analytical_manual=compute_analytical_manual, + dim_points=dim_points, + lin_space_z=lin_space_z, + t_step=tau_step, + sn=sn) + fp_data_b = analytical.get_sw_continuous_prob( + s_rho_0=s_rho_0_b, + tau=tau, + delta=delta, + i=i, + h=h, + rho_0_at_pi=rho_0_at_pi, + compute_fvm=False, + compute_analytical_sw=True, + compute_analytical_manual=compute_analytical_manual, + dim_points=dim_points, + lin_space_z=lin_space_z, + t_step=tau_step, + sn=sn) + fp_time = fp_data_a['time']*np.abs(llg_o._get_tau_d()) + if compute_analytical_manual: + fp_a_ps_manual = fp_data_a['manual_prob'] + # fp_b_ps_manual = fp_data_b['manual_prob'] + else: + fp_a_ps_manual = None + # fp_a_psmanual = None + fp_a_ps = fp_data_a['analytical_prob'] + fp_b_ps = fp_data_b['analytical_prob'] + fp_data_a = fp_data_a['pdf'].T + fp_data_b = fp_data_b['pdf'].T + fp_a_switching_idx = np.min([len(fp_a_ps)-1, + np.searchsorted(fp_a_ps, 0.5)]) + fp_b_switching_idx = np.min([len(fp_b_ps)-1, + np.searchsorted(fp_b_ps, 0.5)]) + print(f'fp_a_switching_idx{fp_a_switching_idx}') + print(f'fp_b_switching_idx{fp_b_switching_idx}') + print(f'fp_time: {fp_time.shape}') + print(f'fp_theta: {fp_theta.shape}') + print(f'fp_data_a: {fp_data_a.shape}') + + ########################################################## + # plot + ########################################################## + if False and plot_intermediate: + fig, axs = plt.subplots(3, 1, + sharex=True, + figsize=(8, 6)) + cmap = plt.cm.cividis + # cmap = plt.cm.PuBu_r + + # s-LLGS + v_min = np.max([1e-8, np.min(sllgs_data)]) + v_max = np.max(sllgs_data) + + sllgs_data[sllgs_data < v_min] = v_min + fp_data_a[fp_data_a < v_min] = v_min + fp_data_b[fp_data_b < v_min] = v_min + + print(f'ranges: {v_min}, {v_max}') + ax0 = axs[0] + ax0.pcolormesh(1e9*sllgs_time, + np.pi-fp_theta, + sllgs_data, + # vmin=v_min, + norm=mcolors.LogNorm(vmin=v_min, + vmax=v_max), + cmap=cmap, + # shading='auto') + shading='gouraud') + ax0.set_ylabel(r'$10^5$ s-LLGS sims' + r'$\theta$ [rad]') + ax00 = ax0.twinx() + ax00.plot([1e9*sllgs_time[sllgs_sw_idx], + 1e9*sllgs_time[sllgs_sw_idx]], + [0, 1], + '--', color='tomato') + ax00.plot([1e9*(t_pulse), + 1e9*(t_pulse)], + [0, 1], + '--', color='tomato') + # axx.plot([0, 1e9*time_tols[-1]], + # [0.5, 0.5], + # '--', color='tomato') + ax00.plot(1e9*sllgs_time, sllgs_sw_ps, + '--', color='whitesmoke') + # ax11.grid() + ax00.set_ylabel(r'Switching probability') + ax00.annotate(f'sw @ {1e9*sllgs_time[sllgs_sw_idx]:.2f} ns', + xy=(1e9*sllgs_time[sllgs_sw_idx], 0.5), + xytext=(1e9*sllgs_time[sllgs_sw_idx]+1, 0.5), + color='whitesmoke' + ) + ax00.annotate(f'write ends @ {1e9*(t_pulse):.2f} ns', + xy=(1e9*(t_pulse), 0.5), + xytext=(1e9*(t_pulse) + 1, 0.5), + color='whitesmoke' + ) + + ax2 = axs[1] + ax3 = axs[2] + + # FP a + ax2.pcolormesh(1e9*fp_time, fp_theta, fp_data_b, + norm=mcolors.LogNorm(vmin=v_min, + vmax=v_max), + cmap=cmap, + shading='auto') # , vmax=v_max) + # ax2.set_title('FPE simulations (Maxwell Prob)') + ax2.set_ylabel( + 'FPE simulation\n(Maxwell Prob)\n' + r'$\theta$ [rad]' + ) + ax2.plot([1e9*fp_time[fp_a_switching_idx], + 1e9*fp_time[fp_a_switching_idx]], + [0, np.pi], + '--', color='tomato') + ax22 = ax2.twinx() + ax22.plot(1e9*fp_time, fp_a_ps, + '--', color='whitesmoke') + ax22.set_ylabel(r'Switching probability') + ax22.plot([1e9*(t_pulse), + 1e9*(t_pulse)], + [0, 1], + '--', color='tomato') + + # FP b + ax3.pcolormesh(1e9*fp_time, fp_theta, fp_data_b, + norm=mcolors.LogNorm(vmin=v_min, + vmax=v_max), + cmap=cmap, + shading='auto') # , vmax=v_max) + # ax4.set_title('FPE simulations (Literature)') + ax3.set_ylabel( + 'FPE simulation\n(Literature Prob)\n' + r'$\theta$ [rad]' + ) + ax3.set_xlabel(r'time [ns]') + ax3.plot([1e9*fp_time[fp_b_switching_idx], + 1e9*fp_time[fp_b_switching_idx]], + [0, np.pi], + '--', color='tomato') + ax22 = ax3.twinx() + ax22.plot(1e9*fp_time, fp_b_ps, + '--', color='whitesmoke') + ax22.plot([1e9*(t_pulse), + 1e9*(t_pulse)], + [0, 1], + '--', color='tomato') + ax22.set_ylabel(r'Switching probability') + + # ax1.set_xlim([0., 1e9*xlim]) + # ax2.set_xlim([0., 1e9*xlim]) + # ax3.set_xlim([0., 1e9*xlim]) + # ax4.set_xlim([0., 1e9*xlim]) + + ax22.annotate(f'write ends @ {1e9*(t_pulse):.2f} ns', + xy=(1e9*(t_pulse), 0.5), + xytext=(1e9*(t_pulse) + 1, 0.5), + color='whitesmoke' + ) + ax22.annotate(f'write ends @ {1e9*(t_pulse):.2f} ns', + xy=(1e9*(t_pulse), 0.5), + xytext=(1e9*(t_pulse) + 1, 0.5), + color='whitesmoke' + ) + + ax2.annotate(f'sw @ {1e9*fp_time[fp_a_switching_idx]:.2f} ns', + xy=(1e9*fp_time[fp_a_switching_idx], 0.5), + xytext=(1e9*fp_time[fp_a_switching_idx]+1, 0.5), + color='whitesmoke' + ) + ax3.annotate(f'sw @ {1e9*fp_time[fp_b_switching_idx]:.2f} ns', + xy=(1e9*fp_time[fp_b_switching_idx], 0.5), + xytext=(1e9*fp_time[fp_b_switching_idx]+1, 0.5), + color='whitesmoke' + ) + for ax in axs: + ax.set_yticks([0, np.pi/4, np.pi/2, 3*np.pi/4, np.pi]) + ax.set_yticklabels([r'$0$', r'$\pi/4$', + r'$\pi/2$', r'$3\pi/4$', r'$\pi$']) + + plt.show() + return {'fp_time': fp_time, + 'fp_theta': fp_theta, + 'fp_data': fp_data_b, + 'fp_ps': fp_a_ps, + 'fp_ps_manual': fp_a_ps_manual, + 'sllgs_time': sllgs_time, + 'sllgs_theta': theta_axis, + 'sllgs_data': sllgs_data, + 'sllgs_ps': sllgs_sw_ps + } + + +################################ +# comparison +################################ + +if compute_analytical_manual: + ps_fp_manual = [None] * len(t_delays_ns) + +plot_idx = 3 +for idx in range(len(sllgs_files)): + # if idx%4 != plot_idx: + # continue + print('---------------------', idx) + data = analyze_s_llgs_tolerances( + sllgs_files[idx], + sllgs_time_files[idx], + t_delays_ns[idx]*1e-9, + I0_uA[idx], + compute_analytical_manual=compute_analytical_manual, + plot_intermediate=plot_intermediate) + ps_sslgs[idx] = data['sllgs_ps'] + time_sslgs[idx] = data['sllgs_time'] + ps_fp[idx] = data['fp_ps'] + time_fp[idx] = data['fp_time'] + if compute_analytical_manual: + ps_fp_manual[idx] = data['fp_ps_manual'] + +fig, ax = plt.subplots(1, 1, + sharex=True, + figsize=(8, 6)) +markers = itertools.cycle( + ('o', '*', 'd', '1', ',', '+', '.', 's', 'X', 'x')) +for idx in range(len(sllgs_files)): + # if idx%4 != plot_idx: + # continue + print('---------------------', idx) + color = next(colors) + marker = next(markers) + np.savetxt(f'dummy_sllgs_wer_{idx}.csv', + np.array([time_sslgs[idx], 1-ps_sslgs[idx]])) + ax.plot(1e9*time_sslgs[idx], 1-ps_sslgs[idx], + # label=sllgs_files[idx].split('/')[-1], + label='10000 s-LLGS walks', + marker=marker, + color=color) + ax.plot(1e9*time_fp[idx], + 1-ps_fp[idx], + '--', + # label=f'fp {sllgs_files[idx]}', + label='Fokker-Plank', + color=color) + if compute_analytical_manual: + ax.plot(1e9*time_fp[idx], + 1-ps_fp_manual[idx], + ':', + # label=sllgs_files[idx], + color=color) +# ax.plot(1e9*fp_time, 1-fp_b_ps, label='fpb') +# ax.plot(1e9*time, 1-sw_ps, '--', label='sllgs') +ax.set_title('WER over time') +ax.set_xlabel('time [a.u.]') +ax.set_ylabel('WER') +ax.set_yscale('log', base=10) +ax.legend() +ax.grid() +plt.show() diff --git a/src/sllgs_fpe_comparison/python_compact_model b/src/sllgs_fpe_comparison/python_compact_model new file mode 120000 index 0000000..d54ec3c --- /dev/null +++ b/src/sllgs_fpe_comparison/python_compact_model @@ -0,0 +1 @@ +../python_compact_model/ \ No newline at end of file diff --git a/src/sllgs_fpe_comparison/sllgs_importer.py b/src/sllgs_fpe_comparison/sllgs_importer.py new file mode 100644 index 0000000..01047d9 --- /dev/null +++ b/src/sllgs_fpe_comparison/sllgs_importer.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# coding: utf-8 +# +# Copyright (c) 2020-2021 Arm Ltd. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""s-LLGs importing tool.""" + +import numpy as np + + +def process_sllgs_data(data_file, time_file, _t_delay, dim_points): + """ + Process s-LLGS. + + Note it removes the delayed samples. + """ + print(f'[debug] reading {data_file}') + data_tol = np.loadtxt(data_file, delimiter=' ') + _time_tol = np.loadtxt(time_file, delimiter=' ') + theta_axis = np.linspace(np.pi, 0, dim_points) + + #################################################################### + # Process data and doing FPE + # ~/SharePoint/ProjectEnergyScalableMRAM/docs/essderc_2021/raw_data + #################################################################### + data_tol = np.abs(data_tol) + print(data_tol.shape) + x = np.zeros(data_tol.shape[0]*data_tol.shape[1]) + y = np.zeros(data_tol.shape[0]*data_tol.shape[1]) + _sllgs_ps_tol = np.zeros(_time_tol.shape[0]) + for i in range(data_tol.shape[0]): + for j in range(data_tol.shape[1]): + idx = i*data_tol.shape[1] + j + x[idx] = _time_tol[j] + y[idx] = np.abs(data_tol[i, j]) + _s_llgs_bins = [np.max([50, data_tol.shape[1]]), dim_points] + _sllgs_rho, _, _ = np.histogram2d(x, y, + bins=_s_llgs_bins, + density=True) + print(_sllgs_rho.shape) + # normalize and log + _sllgs_tol_sw_idx = 0 + for h_idx, h in enumerate(_sllgs_rho): + area = np.abs(np.trapz(h*np.sin(theta_axis), x=theta_axis)) + _sllgs_rho[h_idx] = h/(2*np.pi*area) + + _sllgs_ps_tol = np.zeros(_time_tol.shape[0]) + _sllgs_tol_sw_idx = 0 + for j in range(data_tol.shape[1]): + _sllgs_ps_tol[j] = float( + np.sum(data_tol[:, j] > np.pi/2))/float(data_tol.shape[0]) + if _sllgs_tol_sw_idx == 0 and _sllgs_ps_tol[j] >= 0.5: + _sllgs_tol_sw_idx = j + print('switch at ', j) + + _sllgs_rho = _sllgs_rho.T + print(f'H_tol shape: {_sllgs_rho.shape}') + # H_tol = np.log(H_tol.T) + print('H computed, sw at {1e9*_time_tol[sllgs_low_tol_switching_idx]}') + rho_0_time_idx = np.searchsorted(_time_tol, _t_delay) + print(f'fitting time: {_time_tol[rho_0_time_idx]}') + # shifting till delay + _time_tol = _time_tol[rho_0_time_idx:] - _t_delay + _sllgs_rho = _sllgs_rho[:, rho_0_time_idx:] + _sllgs_tol_sw_idx -= rho_0_time_idx + _sllgs_ps_tol = _sllgs_ps_tol[rho_0_time_idx:] + return _time_tol, _sllgs_rho, _sllgs_tol_sw_idx, _sllgs_ps_tol diff --git a/src/verilog_a_compact_model/README.md b/src/verilog_a_compact_model/README.md new file mode 100644 index 0000000..460377a --- /dev/null +++ b/src/verilog_a_compact_model/README.md @@ -0,0 +1,20 @@ +## Quick Start & More info +Summary: +* Verilog-a compact models: run the testbenches `tb.scs` and `tb_subckt.scs` + +Please, read the full description at [MRAM Framework Description](./doc/README.md). + + +## Files organization +* `doc` + * [README.md](./doc/README.md) for the **full MRAM framework description** +* `src` + * `verilog_a_compact_model` + * [README.md](./verilog_a_compact_model/README.md) for the MRAM verilog-a compact model description + * `tb` Testbenches + * `tb.scs` Example testbench calling full Verilog-a model (conduction and s-LLGS fully written in verilog-a) + * `tb_subckt.scs` Example testbench calling full Spectre subcircuit model (s-LLGS fully written in verilog-a, conduction writen in Spectre) + * `mram_lib` Verilog-a compact model and Spectre library + * `llg_spherical_solver.va` Verilog-a s-LLGS solver, key file + * `*.va` Parameters or auxiliar functions + * `*.scs` Spectre subcircuits and library diff --git a/src/verilog_a_compact_model/mram_lib/conductance_parameters.va b/src/verilog_a_compact_model/mram_lib/conductance_parameters.va new file mode 100644 index 0000000..96902cd --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/conductance_parameters.va @@ -0,0 +1,24 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm LLGs model for PMA-MTJs, conductance parameters + +// use only if required: +// `ifndef _conductance_parameters_ +// `define _conductance_parameters_ + +`include "disciplines.vams" +`include "constants.vams" + +// PARAMETERS +// TMR and conductance +parameter real p_r_p=6e3; // RP resistance, [ohm] +parameter real p_tmr = 2*p_p*p_p/(1-p_p*p_p); +parameter real p_r_contact = 2; + +// use only if required: +// `endif diff --git a/src/verilog_a_compact_model/mram_lib/dimension_parameters.va b/src/verilog_a_compact_model/mram_lib/dimension_parameters.va new file mode 100644 index 0000000..63e5a6f --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/dimension_parameters.va @@ -0,0 +1,30 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm LLGs model for PMA-MTJs, dimension parameters + +// use only if required: +// `ifndef _dimension_parameters_ +// `define _dimension_parameters_ + +`include "disciplines.vams" +`include "constants.vams" + + +// PARAMETERS +// dimmensions +parameter real p_t_fl=1.0e-9; // [m] thickness of free layer +parameter real p_t_ox=1.6e-9; // [m] thickness of oxide +parameter real p_w_fl=50e-9; // [m] mtj width +parameter real p_l_fl=50e-9; // [m] mtj length + + +parameter real _area = `M_PI/4*p_w_fl*p_l_fl; // [m^2} +parameter real _volume = _area*p_t_fl; // [m^3] + +// use only if required: +// `endif diff --git a/src/verilog_a_compact_model/mram_lib/llg_parameters.va b/src/verilog_a_compact_model/mram_lib/llg_parameters.va new file mode 100644 index 0000000..0fabef3 --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/llg_parameters.va @@ -0,0 +1,91 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm LLGs model for PMA-MTJs, s-LLGS parameters + +// use only if required +// `ifndef _llg_parameters_ +// `define _llg_parameters_ + +`include "disciplines.vams" +`include "constants.vams" + +// PARAMETERS +parameter real p_atol=1e-8; + +// thermal handling +parameter integer p_do_thermal=0; // simulate thermal noise +parameter integer p_do_theta_windowing=0; // force a min theta angle + // (set to theta_init) if + // thermal is not computed, + // to avoid total damping +parameter real p_theta_windowing_factor = 3; // defines theta_min = theta_0 * param + // if windowed +parameter integer p_do_fake_thermal=1; // simulate fake thermal noise +parameter real p_d_theta_fake_th=0.01; // d_theta will have an extra + // term based on this +// randomness +parameter real p_seed=0; + +// pinned layer angle +parameter real p_theta_pl=0.0; // [rad] pinned layer theta +parameter real p_phi_pl=0.0; // [rad] pinned layer phi + +// initial angle +parameter integer p_state_init = 2; // 0: P, 1: AP, 2: theta_init +parameter real p_theta_init=0.01414119; // [rad] + // if state_init 0/1: + // -> (pi -) 1/sqrt(2delta(T)) +parameter real p_phi_init=0.7854; // [rad] + +// magnetic parameters +parameter real p_k_i_0=1e-3; // uniaxial anisotropy [J/m^2] +parameter real p_alpha=0.01; // alpha damping factor +parameter real p_ms=1.2e6; // magnetization sat. [A/m] +// shape anisotropy +parameter real p_shape_ani_demag_mode=1; // 0: no shape anisotropy +// oommf validation +// parameter real p_shape_ani_demag_mode=1; // 0: no shape anisotropy + // 1: shape_ani_demag_n vector + // 2 and 3: + // two different literature + // implementations +// oomff validation +parameter real p_shape_ani_demag_n_x=0.029; // Shape anisotropy demag vector +parameter real p_shape_ani_demag_n_y=0.029; // if -1, it is computed +parameter real p_shape_ani_demag_n_z=0.941; // as a cylinder +// exchange energy +parameter integer p_do_a_exchange=0; // whether do or not energy + // exchange +parameter real p_a_exchange=1e-11; // Exchange constant [J/m] +// stt parameters +parameter real p_stt_mode=1; // 0: stt_oommf_full: + // lamb. & P free per layer + // 1: stt_oommf_simple: + // single lambda and P + // 2: stt_simplest: just p +// stt mode oomf_full +parameter real p_stt_scale_mult = 1; // 1 matching OOMMF +parameter real p_p_pl=0.3; // polarization factor +parameter real p_p_fl=0.25; // polarization factor +parameter real p_lambda_pl=1.2; // [FITTING] parameter +parameter real p_lambda_fl=1.2; // [FITTING] parameter + // for out of plane torque +// stt mode oomf_simple +parameter real p_lambda_s=1.0; // [FITTING] parameter +parameter real p_p=0.75; // polarization factor +// stt mode simplest +parameter real p_nabla_mult=1; // epsilon = nabla/2 +// secondary STT term +parameter real p_eps_prime=0.0; // [FITTING] constant +// vcma paramters +parameter integer p_do_vcma=0; +parameter real p_xi=61e-15; // vcma coeff, [J/(V*m)] + +//use only if needed +// `endif diff --git a/src/verilog_a_compact_model/mram_lib/llg_spherical_solver.va b/src/verilog_a_compact_model/mram_lib/llg_spherical_solver.va new file mode 100644 index 0000000..543ff60 --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/llg_spherical_solver.va @@ -0,0 +1,903 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm LLGs model for PMA-MTJs, s-LLGS solver + +`ifndef _llg_spherical_solver_ +`define _llg_spherical_solver_ + +`include "disciplines.vams" +`include "constants.vams" + +// fl or nothing: free layer +// pl: pinned layer + +// Behavioral Model for a single magnetic tunnel junction +module LLGs_va (i_mtj, v_mtj, v_m_z, h_ext_x, h_ext_y, h_ext_z); + +// functions +`include "vectorial_fn.va" +// constants +`include "physical_constants.va" +// parameters +`include "llg_parameters.va" +`include "dimension_parameters.va" +`include "conductance_parameters.va" + +ground gnd; + +input i_mtj, v_mtj; +output v_m_z; +input h_ext_x, h_ext_y, h_ext_z; + +// electrical variables +electrical h_ext_x, h_ext_y, h_ext_z; +// current, voltage +current i_mtj; +voltage v_mtj; +voltage v_m_z; + +// seed +integer seed; + +// last time +real t_prev; +real dt; + +// reduced gamma [A/m] +real c_gamma; + +// relevant angles +real theta_init, phi_init; +real theta_rand; +real theta_0, theta_min; + +// magnetic vector differentials +// real sin_min_theta; +real d_m_theta, d_m_phi; +real d_fake_th_theta; +// magnetic vector +real m_rho, m_theta, m_phi; +real m_x, m_y, m_z; +// pinned layer magnetic vector +real pl_x, pl_y, pl_z; + +// h_eff_cartesians +real h_eff_x, h_eff_y, h_eff_z; +// noise +real h_th_x, h_th_y, h_th_z; +// h_stt1 and h_stt2 for in-out/plane stt components +real epsilon, beta; +real h_stt_1_x, h_stt_1_y, h_stt_1_z; +real h_stt_2_x, h_stt_2_y, h_stt_2_z; +// h_aux_cartesians +real h_inc_aux_x, h_inc_aux_y, h_inc_aux_z; + +// spherical components +real d_h_eff_theta, d_h_eff_phi, d_h_stt_1_theta, d_h_stt_1_phi, d_h_stt_2_theta, d_h_stt_2_phi; + +// mdp product +real mdp; + +// unit vector terms +real dr_theta_x, dr_theta_y, dr_theta_z; +real dr_phi_x, dr_phi_y, dr_phi_z; + +// ms and ki (dependent on T) +real ms; +real k_i; +// thermal stability +real h_k_0; +real thermal_stability; +real th_power_noise_std; + +// STT related constants/magnitudes following OOMMF. +// three different modes: +// a) OOMMFC, where lambda_pl != lambda_fl +// see full equation at +// https://kelvinxyfong.wordpress.com/research/research-interests +// /oommf-extensions/oommf-extension-xf_thermspinxferevolve/ +// b) OOMMFC, where lambda_pl == lambda_fl == lambda +// c) stt_simplest: +// Khvalkovskiy, A. V., et. al. +// Basic principles of STT-MRAM cell operation in memory arrays. +// Journal of Physics D: Applied Physics, 46(7), 074001. +// https://doi.org/10.1088/0022-3727/46/7/074001 +// +// precomputed once for efficiency +real _stt_scale_fac; +real _lamb_PL2, _lamb_FL2; +real _ap, _am, _ap2, _am2; +real _kp, _km; +real _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + +// shape anisotropy demag_n +real shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z; + +// aux variable for user defined fn +real fn; +real init_aux; + +// Inits shape anisotropy constants. +analog function real initialize_shape_anisotropy_demag; + output shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z; + real shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z, + r, nz; + begin + // mode 0, no shape anisotropy + if (p_shape_ani_demag_mode == 0) begin + shape_ani_demag_n_x = 0.0; + shape_ani_demag_n_y = 0.0; + shape_ani_demag_n_z = 0.0; + end + // mode 0.5, given shape anisotropy + else if (p_shape_ani_demag_mode == 1 && + p_shape_ani_demag_n_x >= 0 && + p_shape_ani_demag_n_y >= 0 && + p_shape_ani_demag_n_z >= 0) begin + shape_ani_demag_n_x = p_shape_ani_demag_n_x; + shape_ani_demag_n_y = p_shape_ani_demag_n_y; + shape_ani_demag_n_z = p_shape_ani_demag_n_z; + end + else if (p_shape_ani_demag_mode == 2) begin + // ferromagnetic cylinder + // Sato, M., & Ishii, Y. (1989). + // Simple and approximate expressions of demagnetizing + // factors of uniformly magnetized rectangular + // rod and cylinder. + // Journal of Applied Physics, 66(2), 983–985. + // https://doi.org/10.1063/1.343481 + r = $sqrt(p_w_fl*p_l_fl)/2; + nz = 1/(2*p_t_fl/(r*$sqrt(`M_PI)) + 1); + shape_ani_demag_n_x = (1-nz)/2; + shape_ani_demag_n_y = (1-nz)/2; + shape_ani_demag_n_z = nz; + end + else if (p_shape_ani_demag_mode == 3) begin + // Zhang, K., et al. + // Compact Modeling and Analysis of Voltage-Gated + // Spin-Orbit Torque Magnetic Tunnel Junction. + // IEEE Access, 8, 50792–50800. + // https://doi.org/10.1109/ACCESS.2020.2980073 + shape_ani_demag_n_x = `M_PI*p_t_fl / (4*$sqrt(p_w_fl*p_l_fl)); + shape_ani_demag_n_y = `M_PI*p_t_fl / (4*$sqrt(p_w_fl*p_l_fl)); + shape_ani_demag_n_z = 1 - 2 * `M_PI*p_t_fl/(4*$sqrt(p_w_fl*p_l_fl)); + end + else begin + $strobe("\n\n**************************************"); + $strobe("\t[ERROR] in anisotropy definition."); + end + // dummyV + initialize_shape_anisotropy_demag = 0; + end +endfunction + +analog function real initialize_stt_oommf_full; + input ms; + output _stt_scale_fac, _ap, _am, _kp, _km; + real ms, + _stt_scale_fac, _ap, _am, _kp, _km, + // internal aux + _lamb_PL2, _lamb_FL2, _ap2, _am2; + begin + _stt_scale_fac = p_stt_scale_mult * c_HBAR / ( + c_U0 * c_E * ms * _volume); + _lamb_PL2 = p_lambda_pl*p_lambda_pl; + _lamb_FL2 = p_lambda_fl*p_lambda_fl; + _ap = $sqrt(_lamb_PL2+1)*(_lamb_FL2+1); + _am = $sqrt(_lamb_PL2-1)*(_lamb_FL2-1); + _ap2 = (_lamb_PL2+1)*(_lamb_FL2+1); + _am2 = (_lamb_PL2-1)*(_lamb_FL2-1); + _kp = p_p_pl*_lamb_PL2 * $sqrt((_lamb_FL2+1)/(_lamb_PL2+1)) + + p_p_fl * _lamb_FL2*$sqrt((_lamb_PL2-1)/(_lamb_FL2-1)); + _km = p_p_pl*_lamb_PL2 * $sqrt((_lamb_FL2+1)/(_lamb_PL2+1)) - + p_p_fl * _lamb_FL2*$sqrt((_lamb_PL2-1)/(_lamb_FL2-1)); + // dummyV + initialize_stt_oommf_full = 0; + end +endfunction + +analog function real initialize_stt_oommf_simple; + input ms; + output _stt_scale_fac, _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + real ms, + _stt_scale_fac, _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + begin + _stt_scale_fac = p_stt_scale_mult * c_HBAR / ( + c_U0 * c_E * ms * _volume); + _eps_simple_num = p_p*p_lambda_s*p_lambda_s; + _eps_simple_den0 = p_lambda_s*p_lambda_s + 1; + _eps_simple_den1 = p_lambda_s*p_lambda_s - 1; + // dummyV + initialize_stt_oommf_simple = 0; + end +endfunction + +analog function real initialize_stt_simplest; + input ms; + output _stt_scale_fac; + real _stt_scale_mult, ms, _volume, + _stt_scale_fac; + begin + _stt_scale_fac = p_stt_scale_mult * c_HBAR / ( + c_U0 * c_E * ms * _volume); + // epsilon = nabla/2 + // nabla = _nabla_mult * P/(1+/-P^2) + // so epsilon = _nabla_mult/2 * P/(1+/-P^2) + // see model code, different research groups + // use different factors for SMTJ + // dummyV + initialize_stt_simplest = 0; + end +endfunction + +analog function real initialize_stt_constants; + input ms; + output _stt_scale_fac, _ap, _am, _kp, _km, + _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + real ms, + _stt_scale_fac, _ap, _am, _kp, _km, + _eps_simple_num, _eps_simple_den0, _eps_simple_den1, + fn; + begin + if (p_stt_mode==0) begin // 0: stt_oommf_full: + fn = initialize_stt_oommf_full( + // inputs + ms, + // outputs + _stt_scale_fac, _ap, _am, _kp, _km); + end + else if (p_stt_mode==1) begin // 1: stt_oommf_simple: + fn = initialize_stt_oommf_simple( + // inputs + ms, + // outputs + _stt_scale_fac, _eps_simple_num, _eps_simple_den0, _eps_simple_den1); + end + else if (p_stt_mode==2) begin // simplest, just p + fn = initialize_stt_simplest( + // inputs + ms, + // outputs + _stt_scale_fac); + end + else begin + $strobe("\n\n**************************************"); + $strobe("\t[ERROR] in STT mode definition."); + end + // dummy + fn = 0; + end +endfunction + +// Init thermal constants. +analog function real initialize_thermal_constants; + input ms, _volume; + output th_power_noise_std; + real ms, _volume, th_power_noise_std; + begin + + // Equation from newest Purdue's paper + // Torunbalci, M. M., et. al (2018). + // Modular Compact Modeling of MTJ Devices. + // IEEE Transactions on Electron Devices, 65(10), 4628–4634. + // https:#doi.org/10.1109/TED.2018.2863538 + // and + // De Rose, R., et al. + // A Variation-Aware Timing Modeling Approach for Write Operation + // in Hybrid CMOS/STT-MTJ Circuits. + // IEEE Transactions on Circuits and Systems I: Regular Papers, + // 65(3), 1086–1095. + // https:#doi.org/10.1109/TCSI.2017.2762431 + // Please, + // note it is different from: + // Ament, S., Rangarajan, N., Parthasarathy, A., & Rakheja, S. (2016). + // Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski equation + // for monodomain nanomagnets : A survey and analysis + // of numerical techniques. 1–19. + // http://arxiv.org/abs/1607.04596 + + // We also include the (1+alpha^2) effect from + // Lee, H., Lee, A., Wang, S., Ebrahimi, F., Gupta, + // P., Khalili Amiri, P., & Wang, K. L. (2018). + // Analysis and Compact Modeling of Magnetic Tunnel Junctions + // Utilizing Voltage-Controlled Magnetic Anisotropy. + // IEEE Transactions on Magnetics, 54(4). + // https://doi.org/10.1109/TMAG.2017.2788010 + + // Also, note that the field generated later is + // sqrt(2 alpha temp Kb / ((1+alpha^2) u0 gamma Ms V dt)) + + // Units: A/m -> (note gamma = c_GAMMA_0*c_U0) + // (J/K) * K /(H/m *A/m * m/C * m^3 * s) + // th_gamma = c_GAMMA_0 * c_U0 / (1 + p_alpha*p_alpha) + + th_power_noise_std = $sqrt(2 * p_alpha * $temperature * c_KB / + (c_U0 * c_GAMMA_0 * c_U0 / (1 + p_alpha*p_alpha) * ms * _volume)); + // dummyV + initialize_thermal_constants = 0; + end +endfunction + +// init magnetic vectors +analog function real initialize_magnetic_vectors; + input theta_init, phi_init, theta_0, theta_rand; + output theta, phi, mx, my, mz, plx, ply, plz, theta_min; + real theta_init, phi_init, state, theta_0, theta_rand, + theta, phi, mx, my, mz, plx, ply, plz, theta_min, + theta_init_aux, fn; + begin + // initial angles under no variability + + // mean angle in maxwell boltzman distribution: + // 2*scale*$sqrt(2/`M_PI)+loc + // default theta_init if theta was not specified + if (p_state_init != 2) begin + if (theta_init < `M_PI/2) begin + theta_init_aux = 2*theta_0*$sqrt(2/`M_PI) + 0; + end + else begin + theta_init_aux = `M_PI - (2*theta_0*$sqrt(2/`M_PI) + 0); + end + end + else begin + theta_init_aux = theta_init; + end + + // initial angle variability, + // f noise is present and an initial angle has not been passed + if (p_do_thermal) begin + if (theta_init_aux < `M_PI/2) begin + theta_init_aux = theta_rand; + end + else begin + theta_init_aux = `M_PI - theta_rand; + end + end + + // windowing case + theta_min = p_theta_windowing_factor * theta_0; + // force min theta even when passed initial params + // in case it is out of boundaries + if (p_do_theta_windowing) begin + if (theta_init_aux > `M_PI/2 && (`M_PI-theta_init_aux < theta_min)) begin + theta_init_aux = `M_PI - theta_min; + end + else if (theta_init_aux < theta_min) begin + theta_init_aux = theta_min; + end + $strobe("windowing theta_init_aux: %f", theta_init_aux); + end + + // init vectors + theta = theta_init_aux; + phi = phi_init; + fn = cart_from_spherical(1.0, theta, phi, mx, my, mz); + fn = cart_from_spherical(1.0, p_theta_pl, p_phi_pl, plx, ply, plz); + // dummyV + initialize_magnetic_vectors = 0; + end +endfunction + +// shape anisotropy demagnetization +analog function real get_h_demag; + input mx, my, mz, ms, nx, ny, nz; + output hx, hy, hz; + real mx, my, mz, ms, nx, ny, nz, + hx, hy, hz; + begin + // Get H_demag field vector due to shape anisotropy in the FL. + // + // Zhang, K., et. al. + // Compact Modeling and Analysis of Voltage-Gated Spin-Orbit + // Torque Magnetic Tunnel Junction. + // IEEE Access, 8, 50792–50800. + // https://doi.org/10.1109/ACCESS.2020.2980073 + // + // Full anisotropy: + // Watanabe, K., + // Shape anisotropy revisited in single-digit nanometer magnetic + // tunnel junctions. + // Nature Communications, 9(1), 5–10. + // https://doi.org/10.1038/s41467-018-03003-7 + // given [nx, ny, xz] + hx = -ms*nx * mx; + hy = -ms*ny * my; + hz = -ms*nz * mz; + // dummyV + get_h_demag = 0; + end +endfunction + +// VCMA +analog function real get_h_vcma; + input v_mtj, ms; + output hz; + real v_mtj, ms, + hz; + begin + // Get VCMA vector. + // Zhang, K., et. al. + // Compact Modeling and Analysis of Voltage-Gated Spin-Orbit + // Torque Magnetic Tunnel Junction. + // IEEE Access, 8, 50792–50800. + // https://doi.org/10.1109/ACCESS.2020.2980073 + if (!p_do_vcma) begin + hz = 0.0; + end + else begin + hz = -2 * p_xi * v_mtj / (p_t_fl * p_t_ox * c_U0 * ms); + end + // dummyV + get_h_vcma = 0; + end +endfunction + +// uniaxial anisotropy +analog function real get_h_unia; + input mz, ki, ms; + output hz; + real mz, ki, ms, + hz; + begin + + // We consider interfacial PMA anisotropy. + // The geometry defines it. + // See Figure 2 at + // Khvalkovskiy, A. V., et. al. + // Basic principles of STT-MRAM cell operation in memory arrays. + // Journal of Physics D: Applied Physics, 46(7), 074001. + // https://doi.org/10.1088/0022-3727/46/7/074001 + // + // Full anisotropy: + // Watanabe, K., + // Shape anisotropy revisited in single-digit nanometer magnetic + // tunnel junctions. + // Nature Communications, 9(1), 5–10. + // https://doi.org/10.1038/s41467-018-03003-7 + // vector u_anisotropy == unitary z + // # uniaxial aniotropy constant in J/m^3 + // p_k_u = p_thermal_stability * p_temperature * \ + // c_KB / (p__volume) + // print(f'[debug] k_u: {p_k_u}') + // print(f'[debug] thermal stability: {p_thermal_stability}') + // return np.array([0., + // 0., + // 2 * p_k_u * m_cart[2] / (c_U0 * p_ms) + // ]) + hz = 2 * ki * mz / (p_t_fl * c_U0 * ms); + // dummyV + get_h_unia = 0; + end +endfunction + +// STT epsilon +analog function real get_epsilon_stt; + input _kp, _ap, _km, _am, mdp, + _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + // output epsilon; + real _kp, _ap, _km, _am, mdp, + _eps_simple_num, _eps_simple_den0, _eps_simple_den1; + begin + // Compute OOMMF epsilon term based on mdp vector. + + // a) OOMMFC, where lambda_pl != lambda_fl + // b) OOMMFC, where lambda_pl == lambda_fl == lambda + // c) stt_simplest: + // Khvalkovskiy, A. V., et. al. + // Basic principles of STT-MRAM cell operation in memory arrays. + // Journal of Physics D: Applied Physics, 46(7), 074001. + // https://doi.org/10.1088/0022-3727/46/7/074001 + if (p_stt_mode == 0) begin + if (p_lambda_pl == 1.0 || p_lambda_fl == 1.0) begin + get_epsilon_stt = 0.5 * p_p; + end + else begin + get_epsilon_stt = _kp / (_ap + _am * mdp) + _km / (_ap - _am * mdp); + end + end + else if (p_stt_mode == 1) begin + get_epsilon_stt = _eps_simple_num / (_eps_simple_den0 + _eps_simple_den1 * mdp); + end + else if (p_stt_mode == 2) begin + get_epsilon_stt = p_nabla_mult/2*p_p/(1 + p_p * p_p * mdp); + end + else begin + $strobe("[ERROR] Non-valid stt_mode "); + // dummy + get_epsilon_stt = -1; + end + end +endfunction + +analog function real tukey_window; + input x, x_min; + real x, x_min; + begin + // Computes Tukey window + // 0.5*(1 - cos(2*pi*x / (theta_min/2))) + if (x < x_min/4) begin + tukey_window = 0.5*(1 - $cos(2*`M_PI*x / (x_min/2))); + end + else begin + tukey_window = 1.0; + end + end +endfunction + +// theta window definition +analog function real theta_window; + input d_theta, theta, theta_min; + real d_theta, theta, theta_min; + begin + // Windowing fn for theta. + // Requires theta between [0, np.pi], not an open integration. + if ((theta > `M_PI/2 && d_theta < 0) || (theta < `M_PI/2 && d_theta > 0)) begin + theta_window = 1.0; + end + else if (theta >= `M_PI/2 && (`M_PI - theta > theta_min)) begin + // x = -theta + (pi - theta_min) + theta_window = tukey_window(-theta + (`M_PI-theta_min), theta_min); + end + else if (theta <= `M_PI/2 && theta > theta_min) begin + // x = theta - theta_min + theta_window = tukey_window(theta -theta_min, theta_min); + end + else begin + theta_window = 0.0; + end + end +endfunction + +analog begin : LLGs_behav + + @(initial_step) begin + + if (p_do_thermal + p_do_theta_windowing + p_do_fake_thermal > 1) begin + $strobe("\n\n**************************************"); + $strobe("\t[ERROR], only one thermal field scheme can be selected"); + $fatal(0); + end + // check parameters + // thermal, anysotropy, stt, + + // need of initialization + seed = p_seed; + + // Inits shape anisotropy constants. + fn = initialize_shape_anisotropy_demag( + // inputs + // outputs + shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z); + + k_i = p_k_i_0; + ms = p_ms; + + c_gamma = c_U0 * c_GAMMA_0 / (1 + p_alpha*p_alpha); + + // initial theta_init guess, if not specified + if (p_state_init == 2) begin + theta_init = p_theta_init; + end + // approximate m_cart for initial h_k + // will be updated appropriately in init_mag_vectors + else begin + if (p_state_init == 0) begin + theta_init = 0; + end + else if (p_state_init == 1) begin + theta_init = `M_PI; + end + else begin + $strobe("\n\n**************************************"); + $strobe("\t[ERROR] not supported state_init."); + $fatal(0); + end + end + // p_phi_init always provided + phi_init = p_phi_init; + + // thermal stability + // h_k perpendicular anisotropy component + h_k_0 = 2 * k_i * abs($cos(theta_init)) / (p_t_fl * c_U0 * ms); + // h_k demag component + h_k_0 = h_k_0 - ms*shape_ani_demag_n_z * abs($cos(theta_init)); + // VCMA not considered as no voltage is applied at the begining + thermal_stability = (h_k_0 * c_U0 * ms * _volume) / (2 * c_KB * $temperature); + // theta_0 + theta_0 = $sqrt(1/(2*thermal_stability)); + + // STT constants, see llg_parameters.va + fn = initialize_stt_constants( + // inputs + ms, + // outputs + _stt_scale_fac, _ap, _am, _kp, _km, + _eps_simple_num, _eps_simple_den0, _eps_simple_den1); + + // thermal constants + fn = initialize_thermal_constants(ms, _volume, th_power_noise_std); + + // we could add 1ns of stabilization before injecting current + // or, by adding noise with gaussian/maxwell-boltzmann distribution, + // where the second moment gives + // the thermal average (theta_0 square) + + // # Maxwell-Boltzmann distribution info: + // a) Switching distributions for perpendicular spin-torque devices + // within the macrospin approximation. + // IEEE Transactions on Magnetics, 48(12), 4684–4700. + // https://doi.org/10.1109/TMAG.2012.2209122 + // b) Khvalkovskiy, A. V., et. al. + // Basic principles of STT-MRAM cell operation in memory arrays. + // Journal of Physics D: Applied Physics, 46(7), 074001. + // https://doi.org/10.1088/0022-3727/46/7/074001 + + // theta_0 can be given by 1/sqrt(2*delta) (most common aproach) + // a) Sun, J. Z. (2006). + // Spin angular momentum transfer in current-perpendicular + // nanomagnetic junctions. IBM Journal of Research and Development + // https://doi.org/10.1147/rd.501.0081< + // Butler, W. H., et al. + // b) Switching distributions for perpendicular spin-torque devices + // within the macrospin approximation. + // IEEE Transactions on Magnetics, 48(12), 4684–4700. + // https://doi.org/10.1109/TMAG.2012.2209122 + + // or 1/sqrt(delta) + // c) Khvalkovskiy, A. V., et al. + // Basic principles of STT-MRAM cell operation in memory arrays. + // Journal of Physics D: Applied Physics, 46(7), 074001. + // https://doi.org/10.1088/0022-3727/46/7/074001 + //get maxwell distribution from chi squared distribution + // if X~maxwell then + // X^2 ~ X_chisquared^2(3) + theta_rand = $sqrt($rdist_chi_square(seed, 3))*theta_0; + fn =initialize_magnetic_vectors( + // inputs + theta_init, phi_init, + theta_0, theta_rand, + // outputs + m_theta, m_phi, m_x, m_y, m_z, pl_x, pl_y, pl_z, theta_min); + + // time reference + dt = 0.0; + t_prev = -1e-9; + $strobe("Init abstime: %r, prev: %r", $abstime*1e9, t_prev*1e9); + + $strobe("\n\n**********************************************"); + $strobe("\tidt atol: %e", p_atol); + $strobe("\tdo Thermal Noise: %d", $rtoi(p_do_thermal)); + $strobe("\tdo Theta Thermal Windowing: %d", $rtoi(p_do_theta_windowing)); + $strobe("\t\ttheta_windowing_factor: %r", p_theta_windowing_factor); + $strobe("\tdo Fake Theta: %d", $rtoi(p_do_fake_thermal)); + $strobe("\t\td_theta_fake_th: %r", $rtoi(p_d_theta_fake_th)); + $strobe("\tSeed: %d", $rtoi(p_seed)); + + $strobe("**********************************************"); + $strobe("\tdo VCMA: %d", $rtoi(p_do_vcma)); + $strobe("\ttheta_0: %f", theta_0); + $strobe("\ttheta_min: %f", theta_min); + $strobe("\tepsilon_0: %f", epsilon); + $strobe("\tthermal_stability: %f", thermal_stability); + $strobe("**********************************************"); + $strobe("shape ani N: [%f, %f, %f]", shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z); + $strobe("\tPL_x: %f, pl_y: %f, pl_z: %f", + pl_x, pl_y, pl_z); + $strobe("\tInitial m_x: %f, m_y: %f, m_z: %f", + m_x, m_y, m_z); + $strobe("\tinitial theta: %f", m_theta); + $strobe("\tRecovered theta: %f, recovered phi: %f", + $acos(m_z), $atan2(m_y, m_x)); + $strobe("\**********************************************\n\n"); + end + + // Compute dm_sph/dt fn. + // + // LLG equation (with secondary stt term) at eq 1.3 + // Switching distributions for perpendicular spin-torque devices + // within the macrospin approximation. + // IEEE Transactions on Magnetics, 48(12), 4684–4700. + // https://doi.org/10.1109/TMAG.2012.2209122 + // see OOMMF for the value of the different terms + // and + // Ament, S., et al. + // Solving the stochastic Landau-Lifshitz-Gilbert-Slonczewski + // equation for monodomain nanomagnets : + // A survey and analysis of numerical techniques. + // 1–19. Retrieved from http://arxiv.org/abs/1607.04596 + + // time reference + dt = $abstime - t_prev; + t_prev = $abstime; + // called twice at t=0: + // if ($abstime == 0.0) begin + // dt = 1e9; + // t_prev = -1e-9; + // end + if ($abstime > 0 && dt <= 0) begin + $strobe("\n\n**************************************"); + $strobe("\t[ERROR] negative/zero time step: %r ns", 1e9*dt); + $fatal(0); + end + + fn = cart_from_spherical(1.0, m_theta, m_phi, m_x, m_y, m_z); + // output m_z for conductance model + V(v_m_z, gnd) <+ m_z; + + // Converting spherical coordinates to Cartesian to check rho evolution + m_rho = $sqrt(m_x*m_x + m_y*m_y + m_z*m_z); + if (m_rho > 1.0001) begin + $strobe("!!rho check @ %g ", m_rho); + end + // m p dot product, defines state + fn = udv_cart(m_x, m_y, m_z, pl_x, pl_y, pl_z, mdp); + + // LLG + // + // [Future] [1] Ms and Ku dependence on temperature + // De Rose, R., et al. + // A Compact Model with Spin-Polarization Asymmetry for Nanoscaled Perpendicular MTJs. + // IEEE Transactions on Electron Devices, 64(10), 4346–4353. + // https://doi.org/10.1109/TED.2017.2734967 + // + // Prajapati, S., et. al. + // Modeling of a Magnetic Tunnel Junction for a + // Multilevel STT-MRAM Cell. + // IEEE Transactions on Nanotechnology, 18, 1005–1014. + // https://doi.org/10.1109/TNANO.2018.2875491 + + + // effective field components + // external field + h_eff_x = V(h_ext_x); + h_eff_y = V(h_ext_y); + h_eff_z = V(h_ext_z); + + // demagnetization anisotropy + fn = get_h_demag( + // inputs + m_x, m_y, m_z, + ms, + shape_ani_demag_n_x, shape_ani_demag_n_y, shape_ani_demag_n_z, + // outputs + h_inc_aux_x, h_inc_aux_y, h_inc_aux_z); + h_eff_x = h_eff_x + h_inc_aux_x; + h_eff_y = h_eff_y + h_inc_aux_y; + h_eff_z = h_eff_z + h_inc_aux_z; + + // uniaxial anisotropy + fn = get_h_unia( + // inputs + m_z, k_i, ms, + // outputs + h_inc_aux_z); + h_eff_z = h_eff_z + h_inc_aux_z; + + // uniaxial anisotropy + fn = get_h_vcma( + // inputs + V(v_mtj), ms, + // outputs + h_inc_aux_z); + h_eff_z = h_eff_z + h_inc_aux_z; + + // thermal induced field + // thermal noise could be handler by simulator as the following white_noise + // if (analysis("noise")) begin + // h_th_x = white_noise(th_noise_std); + // h_th_y = white_noise(th_noise_std); + // h_th_z = white_noise(th_noise_std); + // end + if( p_do_thermal ) begin + h_th_x = $rdist_normal(seed, 0, th_power_noise_std)/$sqrt(dt); + h_th_y = $rdist_normal(seed, 0, th_power_noise_std)/$sqrt(dt); + h_th_z = $rdist_normal(seed, 0, th_power_noise_std)/$sqrt(dt); + h_eff_x = h_eff_x + h_th_x; + h_eff_y = h_eff_y + h_th_y; + h_eff_z = h_eff_z + h_th_z; + end + + // STT in/out plane components + epsilon = get_epsilon_stt( + // oommf_full + _kp, _ap, _km, _am, mdp, + // oommf simple + _eps_simple_num, _eps_simple_den0, _eps_simple_den1); + // beta computation as in OOMMF + // beta units are [A/m] + beta = I(i_mtj) * _stt_scale_fac; + // both eps and eps prime contributions + // h_stt components in [A/m] + h_stt_1_x = beta * (epsilon * pl_x); + h_stt_1_y = beta * (epsilon * pl_y); + h_stt_1_z = beta * (epsilon * pl_z); + h_stt_2_x = beta * (p_eps_prime * pl_x); + h_stt_2_y = beta * (p_eps_prime * pl_y); + h_stt_2_z = beta * (p_eps_prime * pl_z); + + // h_eff_phi (local orthogonal unit vector in the direction + // of increasing phi), etc + // differential terms from cart to spherical + fn = diff_unit_theta_vector( + // inputs + 1.0, m_theta, m_phi, + // outputs + dr_theta_x, dr_theta_y, dr_theta_z); + fn = diff_unit_phi_vector( + // inputs + 1.0, m_theta, m_phi, + // outputs + dr_phi_x, dr_phi_y, dr_phi_z); + + d_h_eff_theta = h_eff_x * dr_theta_x + + h_eff_y * dr_theta_y + + h_eff_z * dr_theta_z; + d_h_eff_phi = h_eff_x * dr_phi_x + + h_eff_y * dr_phi_y + + h_eff_z * dr_phi_z; + d_h_stt_1_theta = h_stt_1_x * dr_theta_x + + h_stt_1_y * dr_theta_y + + h_stt_1_z * dr_theta_z; + d_h_stt_1_phi = h_stt_1_x * dr_phi_x + + h_stt_1_y * dr_phi_y + + h_stt_1_z * dr_phi_z; + d_h_stt_2_theta = h_stt_2_x * dr_theta_x + + h_stt_2_y * dr_theta_y + + h_stt_2_z * dr_theta_z; + d_h_stt_2_phi = h_stt_2_x * dr_phi_x + + h_stt_2_y * dr_phi_y + + h_stt_2_z * dr_phi_z; + + // as specified in OOMMF, gamma([(rad)/s/T]) should interface + // the fields in A/m, so introducing u0 + d_m_theta = c_gamma * (d_h_eff_phi + d_h_stt_1_theta - d_h_stt_2_phi + + p_alpha*(d_h_eff_theta - d_h_stt_1_phi - d_h_stt_2_theta)); + d_m_phi = c_gamma * ( 1 / $sin(m_theta)) * ( + -d_h_eff_theta + d_h_stt_1_phi + d_h_stt_2_theta + + p_alpha*(d_h_eff_phi + d_h_stt_1_theta - d_h_stt_2_phi)); + + // Thermal field handling if thermal field not present + + // fake theta term + // extra term can be seen as a direct contribution to h_phi + // as d_theta/dt dependence on h_th is + // ~ c_gamma (h_th_phi + alpha * h_th_theta) + // opposing damping + if (!p_do_thermal && p_do_fake_thermal) begin + d_fake_th_theta = p_d_theta_fake_th * c_gamma * th_power_noise_std / $sqrt(dt); + d_m_theta = d_m_theta -sign(m_theta-`M_PI/2) * d_fake_th_theta; + end + + // saturate d_theta to emulate min field generated + // by the thermal noise, avoiding total damping + if (!p_do_thermal && p_do_theta_windowing) begin + d_m_theta = d_m_theta * theta_window(d_m_theta, m_theta, theta_min); + end + + + // For solving the differential equation (LLG) + // a) providing initial conditions + // b) limiting phi to physical boundaries: idtmod and min/max fn + + m_theta = idt(d_m_theta, theta_init, 0, p_atol); + m_phi = idtmod(d_m_phi, phi_init, 2*`M_PI, 0, p_atol); + + // no abs control + // m_theta = idt(d_m_theta, theta_init); + // m_phi = idtmod(d_m_phi, phi_init, 2*`M_PI); + + // naive integration, do not use + // m_theta = m_theta + d_m_theta*dt; + // m_phi = m_phi + d_m_phi*dt; + +end + +endmodule + +`endif diff --git a/src/verilog_a_compact_model/mram_lib/mtj_lib.scs b/src/verilog_a_compact_model/mram_lib/mtj_lib.scs new file mode 100644 index 0000000..6692b7b --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/mtj_lib.scs @@ -0,0 +1,20 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm LLGs model for PMA-MTJs, mtj lib + +simulator lang=spectre + +library mtj +section tt +include "mtj_subcircuit.scs" + +// other wrappers from pdk, needed +endsection tt +endlibrary mtj + diff --git a/src/verilog_a_compact_model/mram_lib/mtj_structure.va b/src/verilog_a_compact_model/mram_lib/mtj_structure.va new file mode 100644 index 0000000..71f0864 --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/mtj_structure.va @@ -0,0 +1,102 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm MTJ structure, with generic conduction + +`ifndef _mtj_structure_ +`define _mtj_structure_ + +`include "disciplines.vams" +`include "constants.vams" + +// fl: free layer +// pl: pinned layer + +// llg +`include "llg_spherical_solver.va" + +// Behavioral Model for a single magnetic tunnel junction +module mtj_subcircuit(pl, fl, mz, h_ext_x, h_ext_y, h_ext_z); + + +// functions +`include "vectorial_fn.va" +// // constants +`include "physical_constants.va" + +// parameters +`include "llg_parameters.va" +`include "dimension_parameters.va" +`include "conductance_parameters.va" + + +inout fl, pl; +output mz; +inout h_ext_x, h_ext_y, h_ext_z; + +electrical fl, pl; +current i_mtj; +voltage v_mtj, mz; +electrical h_ext_x, h_ext_y, h_ext_z; + +// electrical variables +real r_mtj; + +// seed +integer seed; + + +// sub modules instantiation +// LLGs_va #(.p_do_thermal(p_do_thermal)) +LLGs_va llgs (.i_mtj(i_mtj), + .v_mtj(v_mtj), + .v_m_z(mz), + .h_ext_x(h_ext_x), + .h_ext_y(h_ext_y), + .h_ext_z(h_ext_z) ); + + +analog function real get_r; + // Julliere, M. (1975). + // Tunneling between ferromagnetic films. + // Physics Letters A, 54(3), 225–226. + // https://doi.org/10.1016/0375-9601(75)90174-7 + + // Lee, H., et.al. + // Analysis and Compact Modeling of Magnetic Tunnel Junctions Utilizing + // Voltage-Controlled Magnetic Anisotropy. + // IEEE Transactions on Magnetics, 54(4). + // https://doi.org/10.1109/TMAG.2017.2788010 + input m_z; + real m_z; + begin + get_r = p_r_p*(1 + p_tmr/(p_tmr + 2))/(1 - p_tmr/(p_tmr + 2)*m_z); + end +endfunction + + +analog begin : mtj_behav + + @(initial_step) begin + $strobe("\**********************************************\n\n"); + end + + + // conduction + r_mtj = get_r(V(mz)); + I(i_mtj) <+ I(fl, pl); + V(v_mtj) <+ V(fl, pl); + + // I(fl, pl) <+ V(fl, pl)/r_mtj; + V(fl, pl) <+ I(fl, pl)*r_mtj; + +end + +endmodule + +`endif diff --git a/src/verilog_a_compact_model/mram_lib/mtj_subcircuit.scs b/src/verilog_a_compact_model/mram_lib/mtj_subcircuit.scs new file mode 100644 index 0000000..3792d35 --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/mtj_subcircuit.scs @@ -0,0 +1,58 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm MTJ structure, with generic conduction, in a subcircuit form + +simulator lang=spectre + +ahdl_include "llg_spherical_solver.va" + + +subckt mtj_subcircuit (pl fl mz h_ext_x h_ext_y h_ext_z) +parameters p_do_thermal=0 p_do_theta_windowing=0 p_do_fake_thermal=0 p_atol=1e-6 ++ p_theta_init=0.01414119 ++ p_p=0.75 ++ p_tmr = 2*p_p*p_p/(1-p_p*p_p) ++ p_r_p=6e3 ++ p_r_contact = 2 + +// llgs solver +llgs ( aux_0 aux_1 mz h_ext_x h_ext_y h_ext_z) LLGs_va \ + p_theta_init=p_theta_init \ + p_do_thermal=p_do_thermal \ + p_do_theta_windowing=p_do_theta_windowing \ + p_do_fake_thermal=p_do_fake_thermal \ + p_atol=p_atol + +i_aux (0 aux_0) bsource i=i(r_mtj_module) +v_aux (aux_1 0) bsource v=v(fl, pl) +r_aux (aux_0 0) resistor r=1 + +// simple TMR based conduction +r_mtj_module (fl pl) bsource r=p_r_p*(1 + p_tmr/(p_tmr + 2))/(1 - p_tmr/(p_tmr + 2)*V(mz)) + +ends mtj_subcircuit + +subckt mtj_subcircuit_no_hext (pl fl) +parameters p_do_thermal=0 p_do_theta_windowing=0 p_do_fake_thermal=1 p_atol=1e-6 ++ p_theta_init=0.01414119 + +// __NO_H_EXT__ +V_hext_x h_ext_x 0 vsource type=dc dc=0 +// __NO_H_EXT__ +V_hext_y h_ext_y 0 vsource type=dc dc=0 +// __NO_H_EXT__ +V_hext_z h_ext_z 0 vsource type=dc dc=0 + +mtj (fl pl mz h_ext_x h_ext_y h_ext_z) mtj_subcircuit \ + p_theta_init=p_theta_init \ + p_do_thermal=p_do_thermal \ + p_do_theta_windowing=p_do_theta_windowing \ + p_do_fake_thermal=p_do_fake_thermal \ + p_atol=p_atol +ends mtj_subcircuit_no_hext diff --git a/src/verilog_a_compact_model/mram_lib/physical_constants.va b/src/verilog_a_compact_model/mram_lib/physical_constants.va new file mode 100644 index 0000000..5809346 --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/physical_constants.va @@ -0,0 +1,49 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Arm physical constants + +`ifndef _physical_constants_ +`define _physical_constants_ + +`include "disciplines.vams" +`include "constants.vams" + +// // CONSTANTS +// // gyromagnetic ratio +// // https://www.ctcms.nist.gov/~rdm/mumag.org.html +// // equivalent to c_U0*2.211e5 (ommited often [rad]) * [m/C] = ([rad]) * [ m/A/s] +// `define c_GAMMA_0 1.76e11 // [rad/s/T] +// +// // elementary charge, [C] +// `define c_E `P_Q // 1.602176462e-19 +// // reduced plank's constant [J s] +// `define c_HBAR (`P_H/(2*`M_PI)) // 6.6260755e-34 / 2*PI +// // vacuum permeability, 4 pi / 1e7 [H/m] +// `define c_U0 (4*`M_PI/1e7) // 1.25663706212e-6 +// // boltzmann's constant [J/K] +// `define c_KB 1.3806503e-23 +// // Borh magneton [J/T] +// `define c_UB (c_GAMMA_0 * c_HBAR / 2) + +// gyromagnetic ratio +// https://www.ctcms.nist.gov/~rdm/mumag.org.html +// equivalent to c_U0*2.211e5 (ommited often [rad]) * [m/C] = ([rad]) * [ m/A/s] +parameter real c_GAMMA_0 = 1.76e11; // [rad/s/T] +// elementary charge, [C] +parameter real c_E = `P_Q; // 1.602176462e-19 +// reduced plank's constant [J s] +parameter real c_HBAR = `P_H/(2*`M_PI); // 6.6260755e-34 / 2*PI +// vacuum permeability, 4 pi / 1e7 [H/m] +parameter real c_U0 = 4*`M_PI/1e7; // 1.25663706212e-6 +// boltzmann's constant [J/K] +parameter real c_KB = 1.3806503e-23; +// Borh magneton [J/T] +parameter real c_UB = c_GAMMA_0 * c_HBAR / 2; + +`endif diff --git a/src/verilog_a_compact_model/mram_lib/vectorial_fn.va b/src/verilog_a_compact_model/mram_lib/vectorial_fn.va new file mode 100644 index 0000000..1cb1c9b --- /dev/null +++ b/src/verilog_a_compact_model/mram_lib/vectorial_fn.va @@ -0,0 +1,107 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +// +// Engineer: fernando.garciaredondo@arm.com +// pranay.prabhat@arm.com +// +// Support functions + +// `ifndef _vectorial_fn_ +// `define _vectorial_fn_ + +analog function real sign; + input x; + real x; + begin + if (x >=0) begin + sign=1.0; + end + else begin + sign = -1.0; + end + end +endfunction + +// compute cartesian dot product u * v +analog function real udv_cart; + input u_x,u_y,u_z,v_x,v_y,v_z; + output d; + real u_x, u_y, u_z, v_x, v_y, v_z, d; + begin + d = u_x*v_x + u_y * v_y + u_z * v_z; + // dummy + udv_cart = 0; + end +endfunction + +// compute cartesian vectorial product u^v +analog function real uxv_cart; + input u_x,u_y,u_z,v_x,v_y,v_z; + output w_x, w_y, w_z; + real u_x, u_y, u_z, v_x, v_y, v_z, w_x, w_y, w_z; + begin + w_x = (u_y*v_z - v_y*u_z); + w_y = (-u_x*v_z + u_z*v_x); + w_z = (u_x*v_y - u_y*v_x); + // dummy + uxv_cart = 0; + end +endfunction + +// transform cartesian vector to spherical +analog function real cart_from_spherical; + input rho, theta, phi; + output x, y, z; + real theta, phi, rho, x, y, z; + begin + x = rho * $sin(theta)*$cos(phi); + y = rho * $sin(theta)*$sin(phi); + z = rho * $cos(theta); + // dummy + cart_from_spherical = 0; + end +endfunction + +// compute differential unit vectors + +// Line element (length element) computation +// Compute dr (theta component) +// given infinitesimal displacement from rho, theta, phi. +// note rho==1 +analog function real diff_unit_theta_vector; + input m_sph_rho, m_sph_theta, m_sph_phi; + output dr_theta_x, dr_theta_y, dr_theta_z; + real m_sph_rho, m_sph_theta, m_sph_phi, + dr_theta_x, dr_theta_y, dr_theta_z; + begin + dr_theta_x = $cos(m_sph_theta)*$cos(m_sph_phi); + dr_theta_y = $cos(m_sph_theta)*$sin(m_sph_phi); + dr_theta_z = -$sin(m_sph_theta); + // dummy + diff_unit_theta_vector = 0; + + end +endfunction + +// Line element (length element) computation +// Compute dr (phi component) +// given infinitesimal displacement from rho, theta, phi. +// note rho==1 +analog function real diff_unit_phi_vector; + input m_sph_rho, m_sph_theta, m_sph_phi; + output dr_phi_x, dr_phi_y, dr_phi_z; + real m_sph_rho, m_sph_theta, m_sph_phi, + dr_phi_x, dr_phi_y, dr_phi_z; + begin + dr_phi_x = -$sin(m_sph_phi); + dr_phi_y = $cos(m_sph_phi); + dr_phi_z = 0; + // dummy + diff_unit_phi_vector = 0; + + end +endfunction + +// `endif diff --git a/src/verilog_a_compact_model/tb/tb.scs b/src/verilog_a_compact_model/tb/tb.scs new file mode 100644 index 0000000..b3e07af --- /dev/null +++ b/src/verilog_a_compact_model/tb/tb.scs @@ -0,0 +1,86 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +// example PMA MTJ testbench + +simulator lang=spectre +global 0 + +ahdl_include "../mram_lib/mtj_structure.va" + + +parameters ++ i_dc=0e-6 ++ i_pulse_ap2p=-35e-6 ++ w_pulse=50e-9 ++ input_delay=-2e-9 ++ temp_celsius=26.85 ++ do_thermal=0 ++ do_theta_windowing=0 ++ do_fake_thermal=0 ++ sim_pivrel=0.1 ++ sim_rtol=1e-4 ++ sim_atol=1e-8 ++ maxstep=1e-12 ++ method=traponly +// + method=gear2only +// [noise] +// + sim_rtol=1e-2 +// + sim_atol=1e-5 + + +// AP->P +// IDC0 0 e0 isource type=dc dc=i_dc +// (pl, fl) pl first to keep sm2tj compatibility +ITRAN0 0 e0 isource type=pulse dc=i_dc val0=0 val1=-i_pulse_ap2p width=w_pulse rise=2n delay=input_delay +// __H_EXT__ MTJ_ap2p (e0 0 h_ext_x h_ext_y h_ext_z)\ +// __NO_H_EXT__ +mtj_ap2p (e0 0 mz 0 0 0) mtj_subcircuit + + p_do_thermal=do_thermal + + p_do_theta_windowing=do_theta_windowing + + p_do_fake_thermal=do_fake_thermal + + p_atol=sim_atol + + +//////////////////////////////////////////// +// sim options +highVoltageOptions options highvoltage=yes bin_relref=yes + +simulatorOptions options \ + temp=(temp_celsius) \ + tnom=25 scalem=1.0 scale=1.0 gmin=1e-12 rforce=1 maxnotes=5 maxwarns=5 \ + digits=5 cols=80 \ + reltol=sim_rtol \ + // [noise] for noise sim + // vabstol=1e-2 iabstol=1e-3 \ + // others, not needed + // vabstol=sim_atol iabstol=sim_atol \ + // pivrel=sim_rtol \ + //sensfile="../psf/sens.output" \ + // checklimitdest=psf + +finalTimeOP info what=oppoint where=rawfile +modelParameter info what=models where=rawfile +element info what=inst where=rawfile +outputParameter info what=output where=rawfile +// designParamVals info what+ eters where=rawfile +primitives info what=primitives where=rawfile +subckts info what=subckts where=rawfile +// save all options +saveOptions options save=all subcktprobelvl=3 saveahdlvars=all exportfile="./exported_test.data" +// save selected options +// saveOptions options save=selected subcktprobelvl=3 saveahdlvars=selected exportfile="./exported_test.data" +// required by export + +// parameters to adjust + +// simulate with traponly ++aps for calibrated model + +// tran_tt tran stop=(2*input_delay + w_pulse) write="spectre.ic" writefinal="spectre.fc" \ +tran_tt tran stop=(50n) write="spectre.ic" writefinal="spectre.fc" \ + annotate=status maxiters=5 \ + method=method \ + // maxstep=maxstep \ + // step=maxstep \ diff --git a/src/verilog_a_compact_model/tb/tb_subckt.scs b/src/verilog_a_compact_model/tb/tb_subckt.scs new file mode 100644 index 0000000..b5ad094 --- /dev/null +++ b/src/verilog_a_compact_model/tb/tb_subckt.scs @@ -0,0 +1,86 @@ +// Copyright (c) 2021 Arm Ltd. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +// example PMA MTJ testbench + +simulator lang=spectre +global 0 + +include "../mram_lib/mtj_subcircuit.scs" + + +parameters ++ i_dc=0e-6 ++ i_pulse_ap2p=-35e-6 ++ w_pulse=50e-9 ++ input_delay=-2e-9 ++ temp_celsius=26.85 ++ do_thermal=0 ++ do_theta_windowing=0 ++ do_fake_thermal=0 ++ sim_pivrel=0.1 ++ sim_rtol=1e-4 ++ sim_atol=1e-8 ++ maxstep=1e-12 ++ method=traponly +// + method=gear2only +// [noise] +// + sim_rtol=1e-2 +// + sim_atol=1e-5 + + +// AP->P +// IDC0 0 e0 isource type=dc dc=i_dc +// (pl, fl) pl first to keep sm2tj compatibility +ITRAN0 0 e0 isource type=pulse dc=i_dc val0=0 val1=-i_pulse_ap2p width=w_pulse rise=2n delay=input_delay +// __H_EXT__ MTJ_ap2p (e0 0 h_ext_x h_ext_y h_ext_z)\ +// __NO_H_EXT__ +mtj_ap2p (e0 0 mz 0 0 0) mtj_subcircuit + + p_do_thermal=do_thermal + + p_do_theta_windowing=do_theta_windowing + + p_do_fake_thermal=do_fake_thermal + + p_atol=sim_atol + + +//////////////////////////////////////////// +// sim options +highVoltageOptions options highvoltage=yes bin_relref=yes + +simulatorOptions options \ + temp=(temp_celsius) \ + tnom=25 scalem=1.0 scale=1.0 gmin=1e-12 rforce=1 maxnotes=5 maxwarns=5 \ + digits=5 cols=80 \ + reltol=sim_rtol \ + // [noise] for noise sim + // vabstol=1e-2 iabstol=1e-3 \ + // others, not needed + // vabstol=sim_atol iabstol=sim_atol \ + // pivrel=sim_rtol \ + //sensfile="../psf/sens.output" \ + // checklimitdest=psf + +finalTimeOP info what=oppoint where=rawfile +modelParameter info what=models where=rawfile +element info what=inst where=rawfile +outputParameter info what=output where=rawfile +// designParamVals info what+ eters where=rawfile +primitives info what=primitives where=rawfile +subckts info what=subckts where=rawfile +// save all options +saveOptions options save=all subcktprobelvl=3 saveahdlvars=all exportfile="./exported_test.data" +// save selected options +// saveOptions options save=selected subcktprobelvl=3 saveahdlvars=selected exportfile="./exported_test.data" +// required by export + +// parameters to adjust + +// simulate with traponly ++aps for calibrated model + +// tran_tt tran stop=(2*input_delay + w_pulse) write="spectre.ic" writefinal="spectre.fc" \ +tran_tt tran stop=(50n) write="spectre.ic" writefinal="spectre.fc" \ + annotate=status maxiters=5 \ + method=method \ + // maxstep=maxstep \ + // step=maxstep \