prototype for Verilog-A integration using OSDI and OpenVAF

This initial prototype is capable of performing DC, transient and AC
analysis. Not all features of OSDI are supported yet and there are still
some open questions regarding ngspice integration. However many usecase
already work very well and a large amount of CMC models are supported.
The biggest missing feature right now is noise analysis.

test: test case for diode DC working with SH

test: add transient analysis to osdi_diode test

test: added docu text to osdi_diode test

test: added test case directories

fix: bug in osdi_load

test: small change to netlist

fix: implement DEVunsetup

fix: correct behaviour for MODEINITSMSIG

test: osdi diode enable all analysis modes

removed netlist

ignoring test results

added the build of the diode shared object to the python test script

deleting old stuff and always rebuilding the shared object

added diode_va.c to the repo

preparing CI

Create .gitlab-ci.yml file

(testing) add res, cap and multiple devices test

feat: use osdi command to load files

Previously OSDI shared object files were loaded from fixed directories.
This was unreliable, inconvenient and caused conflicts with XSPICE.

This commit remove the old loading mechanism and instead introduces the
`osdi` command that can load (a list of) osdi object files (like the
codemodel command for XSPICE). A typical usecase will use this as a
precommand in the netlist:

.control
pre_osdi foo.osdi
.endc

If the specified file is a relative path it is first resolved relative
to the parent directory of the netlist. If the osdi command is invoked
from the interactive prompt the file is resolved relative to the current
working directory instead.

This commit also moves osdi from the devices folder to the root src
folder like xspice. This better reflects the role of the code as users
may otherthwise (mistakenly) assume that osdi is just another
handwritten model.

test: update tests to new command

fix: do not ignore first parameter

feat: implement log message callback

fix: don't generate ddt matrix/rhs in DC sweep

fix: missing linker script

update to osdi 0.3

(testing) simplify test cases, fix bug

(testing) multiple devices test improvement

(testig) node collapsing bugfix

test: increase tolerance in tests

feat: update to newest OSDI header

fix: temperature update dt behaviour

fix: ignored models

fix: compilation script

fix: allow hicum/l2 to compile with older c++ compilers

fix: set required compiler flags for osdi

fix: disable x by default

fix: add missing SPICE functions

fix: update diode to latest ngspice version

feat: implement python CMC test runner

doc: Add README_OSDI.md

fix: make testing script work with python version before 3.9

fix: free of undefined local variable

fix: do not calculate time derivative during tran op

update osdi version

fixes for compilation on windows
This commit is contained in:
Pascal Kuthe 2022-04-20 18:12:10 +02:00 committed by Holger Vogt
parent d7bdfe1a20
commit acfaf023b3
65 changed files with 7635 additions and 34 deletions

4
.gitignore vendored
View File

@ -86,3 +86,7 @@ src/spicelib/parser/inpptree-parser.c
src/spicelib/parser/inpptree-parser.h
# Visual Studio Code user options files
.vscode/
test_cases/diode/__pycache__/*
test_cases/diode/test_osdi/*
test_cases/diode/test_built_in/*

View File

@ -74,6 +74,7 @@ Dietmar Warning,
Michael Widlok,
Charles D.H. Williams,
Antony Wilson,
Pascal Kuthe,
and many others...
If someone helped in the development and has not been inserted in this list

380
COPYING
View File

@ -28,6 +28,9 @@ unnamed MIT license, compatible to New BSD
ngspice/src/spicelib/devices/adms/admst
LGPLv2.1
* all files in ngspice/src/osdi
MPLv2.0
ngspice/src/spicelib/devices/ndev
public domain
@ -565,3 +568,380 @@ LICENSE
permitted in any medium without royalty provided the copyright notice
and this notice are preserved. This file is offered as-is, without any
warranty.
-----------------------------MPLv2.0 - OSDI--------------------------------
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM python:3.10.4-bullseye
# python installation
RUN apt-get update && apt-get -y install bc bison flex libxaw7 libxaw7-dev libx11-6 libx11-dev libreadline8 libxmu6
RUN apt-get update && apt-get -y install build-essential libtool gperf libxml2 libxml2-dev libxml-libxml-perl libgd-perl
RUN apt-get update && apt-get -y install g++ gfortran make cmake libfl-dev libfftw3-dev
RUN pip install pytest numpy pandas

33
README_OSDI.md Normal file
View File

@ -0,0 +1,33 @@
# OSDI implementation for NGSPICE
OSDI (Open Source Device Interface) is a simulator independent device interface, that is used by the OpenVAF compiler.
Implementing this interface in NGSPICE allows loading Verilog-A models compiled by OpenVAF.
The interface is fixed and does not require the compiler to know about NGSPICE during compilation.
NGSPICE also doesn't need to know anything about the compiled models at compilation.
Therefore, these models can be loaded dynamically at runtime.
To that end the `osdi` command is provided.
It allows loading a dynamic library conforming to OSDI.
Example usage: `osdi diode.osdi`.
If used within a netlist the command requires the `pre_` prefix.
This ensures that the devices are loaded before the netlist is parsed.
Example usage: `pre_osdi diode.osdi`
If a relative path is provided to the `osdi` command in a netlist, it will resolve that path **relative to the netlist**, not relative to current working directory.
This ensures that netlists can be simulated from any directory
## Build Instructions
To compile NGSPICE with OSDI support ensure that the `--enable-predictor` and `--enable-osdi` flags are used.
The `compile_linus.sh` file enables these flags by default.
## Example/Test Case
A simple handwritten diode can be found in `test_cases/diode/diode.c`.
In the same directory a script named `test_diode.py` is provided that will compile this model and run some example simulations.
After the script has finished the compilation result `diode.osdi` and the netlist can then be found in `test_cases/diode/test_osdi`.

View File

@ -52,14 +52,14 @@ if test "$1" = "d"; then
echo "configuring for 64 bit debug"
echo
# You may add --enable-adms to the following command for adding adms generated devices
../configure --with-x --enable-xspice --enable-cider --with-readline=yes --enable-openmp CFLAGS="-g -m64 -O0 -Wall -Wno-unused-but-set-variable" LDFLAGS="-m64 -g"
../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --with-readline=yes --enable-openmp CFLAGS="-g -m64 -O0 -Wall -Wno-unused-but-set-variable" LDFLAGS="-m64 -g"
else
cd release
if [ $? -ne 0 ]; then echo "cd release failed"; exit 1 ; fi
echo "configuring for 64 bit release"
echo
# You may add --enable-adms to the following command for adding adms generated devices
../configure --with-x --enable-xspice --enable-cider --with-readline=yes --enable-openmp --disable-debug CFLAGS="-m64 -O2" LDFLAGS="-m64 -s"
../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --with-readline=yes --enable-openmp --disable-debug CFLAGS="-m64 -O2" LDFLAGS="-m64 -s"
fi
if [ $? -ne 0 ]; then echo "../configure failed"; exit 1 ; fi

View File

@ -146,6 +146,10 @@ AC_ARG_ENABLE([oldapps],
AC_ARG_ENABLE([xspice],
[AS_HELP_STRING([--enable-xspice], [Enable XSPICE enhancements])])
# --enable-osdi: define OSDI in the code. This is for osdi support
AC_ARG_ENABLE([osdi],
[AS_HELP_STRING([--enable-osdi], [Enable OSDI integration])])
# --enable-cider: define CIDER in the code. This is for CIDER support
AC_ARG_ENABLE([cider],
[AS_HELP_STRING([--enable-cider], [Enable CIDER enhancements])])
@ -1156,7 +1160,18 @@ if test "x$enable_xspice" = xyes; then
AC_SUBST([VIS_CFLAGS])
else
XSPICEINIT="*"
if test "x$enable_osdi" = xyes; then\
case $host_os in
*mingw* | *msys* | *cygwin* | *solaris* )
XSPICEDLLIBS=""
;;
* )
XSPICEDLLIBS="-ldl"
;;
esac
fi
fi
AC_SUBST([XSPICEINIT])
AC_SUBST([XSPICEDLLIBS])
@ -1165,6 +1180,36 @@ AM_CONDITIONAL([XSPICE_WANTED], [test "x$enable_xspice" = xyes])
AM_CONDITIONAL([SHORT_CHECK_WANTED], [test "x$enable_shortcheck" = xyes])
if test "x$enable_osdi" = xyes; then
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
AS_VAR_IF(CACHEVAR,yes,
[m4_default([$2], :)],
[m4_default([$3], :)])
AS_VAR_POPDEF([CACHEVAR])dnl
])dnl AX_CHECK_COMPILE_FLAGS
AX_CHECK_COMPILE_FLAG([-std=c11], [
CFLAGS="$CFLAGS -std=c11"
LDLAGS="$LDLAGS -std=c11"
], [
echo "C compiler cannot compile C11 code"
exit -1
])
AC_MSG_RESULT([OSDI features included])
AC_DEFINE([OSDI], [1], [The OSDI enhancements])
fi
AM_CONDITIONAL([OSDI_WANTED], [test "x$enable_osdi" = xyes])
# Add CIDER enhancements to ngspice.
if test "x$enable_cider" = xyes; then
AC_MSG_RESULT([CIDER features enabled])
@ -1444,6 +1489,7 @@ AC_CONFIG_FILES([Makefile
src/xspice/enh/Makefile
src/xspice/ipc/Makefile
src/xspice/idn/Makefile
src/osdi/Makefile
tests/Makefile
tests/bsim1/Makefile
tests/bsim2/Makefile

View File

@ -7,6 +7,10 @@ if XSPICE_WANTED
SUBDIRS += xspice
endif
if OSDI_WANTED
SUBDIRS += osdi
endif
if CIDER_WANTED
SUBDIRS += ciderlib
endif
@ -97,6 +101,8 @@ DYNAMIC_DEVICELIBS = \
@VLADEV@
## Build ngspice first:
## compile the icon:
@ -116,6 +122,8 @@ ngspice_CPPFLAGS = $(AM_CPPFLAGS) -DSIMULATOR
if WINGUI
ngspice_LDFLAGS = -municode $(AM_LDFLAGS)
ngspice_SOURCES += winmain.c hist_info.c
else
ngspice_LDFLAGS =
endif
ngspice_LDADD = \
@ -157,9 +165,9 @@ ngspice_LDADD += \
xspice/evt/libevtxsp.la \
xspice/enh/libenhxsp.la \
xspice/ipc/libipcxsp.la \
xspice/idn/libidnxsp.la \
@XSPICEDLLIBS@
xspice/idn/libidnxsp.la
endif
ngspice_LDADD += @XSPICEDLLIBS@
ngspice_LDADD += \
frontend/parser/libparser.la \
@ -175,6 +183,10 @@ ngspice_LDADD += \
ciderlib/support/libcidersuprt.la
endif
if OSDI_WANTED
ngspice_LDADD += osdi/libosdi.la
endif
ngspice_LDADD += \
maths/deriv/libderiv.la \
maths/cmaths/libcmaths.la \
@ -439,9 +451,9 @@ libspice_la_LIBADD += \
xspice/evt/libevtxsp.la \
xspice/enh/libenhxsp.la \
xspice/ipc/libipcxsp.la \
xspice/idn/libidnxsp.la \
@XSPICEDLLIBS@
xspice/idn/libidnxsp.la
endif
libspice_la_LIBADD += @XSPICEDLLIBS@
libspice_la_LIBADD += \
frontend/parser/libparser.la \
@ -457,6 +469,11 @@ libspice_la_LIBADD += \
ciderlib/support/libcidersuprt.la
endif
if OSDI_WANTED
libspice_la_LIBADD += osdi/libosdi.la
endif
libspice_la_LIBADD += \
maths/deriv/libderiv.la \
maths/cmaths/libcmaths.la \
@ -558,9 +575,9 @@ libngspice_la_LIBADD += \
xspice/evt/libevtxsp.la \
xspice/enh/libenhxsp.la \
xspice/ipc/libipcxsp.la \
xspice/idn/libidnxsp.la \
@XSPICEDLLIBS@
xspice/idn/libidnxsp.la
endif
libngspice_la_LIBADD += @XSPICEDLLIBS@
libngspice_la_LIBADD += \
frontend/parser/libparser.la \
@ -576,6 +593,9 @@ libngspice_la_LIBADD += \
ciderlib/support/libcidersuprt.la
endif
libngspice_la_LIBADD += osdi/libosdi.la
libngspice_la_LIBADD += \
maths/deriv/libderiv.la \
maths/cmaths/libcmaths.la \

View File

@ -18,6 +18,21 @@ void com_codemodel(wordlist *wl)
}
#endif
#ifdef OSDI
void com_osdi(wordlist *wl)
{
wordlist *ww;
for (ww = wl; ww; ww = ww->wl_next)
if (load_osdi(ww->wl_word)) {
fprintf(cp_err, "Error: Library %s couldn't be loaded!\n", ww->wl_word);
if (ft_stricterror)
controlled_exit(EXIT_BAD);
}
}
#endif
#ifdef DEVLIB
void com_use(wordlist *wl)

View File

@ -5,6 +5,10 @@
void com_codemodel(wordlist *wl);
#endif
#ifdef OSDI
void com_osdi(wordlist *wl);
#endif
#ifdef DEVLIB
void com_use(wordlist *wl);
#endif

View File

@ -268,6 +268,12 @@ struct comm spcp_coms[] = {
NULL,
"library library ... : Loads the code model libraries." } ,
#endif
#ifdef OSDI
{ "osdi", com_osdi, FALSE, TRUE,
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 1, LOTS,
NULL,
"library library ... : Loads a osdi library." } ,
#endif
#ifdef DEVLIB
{ "use", com_use, FALSE, TRUE,
{ 040000, 040000, 040000, 040000 }, E_BEGINNING, 1, LOTS,

View File

@ -18,6 +18,7 @@ Author: 1985 Wayne A. Christopher
#include "ngspice/fteinp.h"
#include "inp.h"
#include "ngspice/osdiitf.h"
#include "runcoms.h"
#include "inpcom.h"
#include "circuits.h"
@ -565,7 +566,6 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile)
if (fp) {
cp_vset("inputdir", CP_STRING, dir_name);
}
tfree(dir_name);
/* if nothing came back from inp_readall, e.g. after calling ngspice without parameters,
just close fp and return to caller */
@ -740,8 +740,16 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile)
before the circuit structure is set up */
if (pre_controls) {
pre_controls = wl_reverse(pre_controls);
for (wl = pre_controls; wl; wl = wl->wl_next)
for (wl = pre_controls; wl; wl = wl->wl_next){
#ifdef OSDI
inputdir = dir_name;
#endif
cp_evloop(wl->wl_word);
}
#ifdef OSDI
inputdir = NULL;
#endif
wl_free(pre_controls);
}
@ -1108,18 +1116,29 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile)
of commands. Thus this is delegated to a function using a third thread, that
only starts when the background thread has finished (sharedspice.c).*/
#ifdef SHARED_MODULE
for (wl = controls; wl; wl = wl->wl_next)
for (wl = controls; wl; wl = wl->wl_next){
#ifdef OSDI
inputdir = dir_name;
#endif
if (cp_getvar("controlswait", CP_BOOL, NULL, 0)) {
exec_controls(wl_copy(wl));
break;
}
else
cp_evloop(wl->wl_word);
}
#else
for (wl = controls; wl; wl = wl->wl_next)
for (wl = controls; wl; wl = wl->wl_next){
#ifdef OSDI
inputdir = dir_name;
#endif
cp_evloop(wl->wl_word);
}
#endif
wl_free(controls);
#ifdef OSDI
inputdir = NULL;
#endif
}
/* Now reset everything. Pop the control stack, and fix up the IO
@ -1131,6 +1150,9 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile)
cp_curerr = lasterr;
tfree(tt);
tfree(dir_name);
return 0;
}

View File

@ -11,6 +11,7 @@ Author: 1985 Wayne A. Christopher
/* Note: Must include shlwapi.h before ngspice header defining BOOL due
* to conflict */
#include <stdio.h>
#ifdef _WIN32
#include <shlwapi.h> /* for definition of PathIsRelativeA() */
#pragma comment(lib, "Shlwapi.lib")
@ -1563,6 +1564,8 @@ struct inp_read_t inp_read( FILE *fp, int call_depth, const char *dir_name,
!ciprefix("wrdata", buffer) &&
!ciprefix(".lib", buffer) && !ciprefix(".inc", buffer) &&
!ciprefix("codemodel", buffer) &&
!ciprefix("osdi", buffer) &&
!ciprefix("pre_osdi", buffer) &&
!ciprefix("echo", buffer) && !ciprefix("shell", buffer) &&
!ciprefix("source", buffer) && !ciprefix("cd ", buffer) &&
!ciprefix("load", buffer) && !ciprefix("setcs", buffer)) {
@ -2352,6 +2355,8 @@ static char *get_subckt_model_name(char *line)
name = skip_non_ws(line); // eat .subckt|.model
name = skip_ws(name);
end_ptr = skip_non_ws(name);
return copy_substring(name, end_ptr);
@ -2401,14 +2406,29 @@ static char *get_model_type(char *line)
}
static char *get_adevice_model_name(char *line)
static char *get_adevice_model_name(char *line, struct nscope *scope)
{
char *ptr_end, *ptr_beg;
char *beg_ptr, *end_ptr, *name;
int i = 0;
ptr_end = skip_back_ws(strchr(line, '\0'), line);
ptr_beg = skip_back_non_ws(ptr_end, line);
beg_ptr = skip_non_ws(line); /* eat device name */
beg_ptr = skip_ws(beg_ptr);
return copy_substring(ptr_beg, ptr_end);
for (i = 0; i < 30; i++) { /* skip the terminals */
end_ptr = skip_non_ws(beg_ptr);
name = copy_substring(beg_ptr, end_ptr);
if (inp_find_model(scope, name)){
return name;
}else if (beg_ptr == end_ptr){
break;
}
end_ptr = skip_ws(end_ptr);
beg_ptr = end_ptr;
}
return NULL;
}
@ -2627,7 +2647,7 @@ static void get_subckts_for_subckt(struct card *start_card, char *subckt_name,
nlist_adjoin(used_subckts, inst_subckt_name);
}
else if (*line == 'a') {
char *model_name = get_adevice_model_name(line);
char *model_name = get_adevice_model_name( line, card->level);
nlist_adjoin(used_models, model_name);
}
else if (has_models) {
@ -2713,7 +2733,7 @@ void comment_out_unused_subckt_models(struct card *start_card)
nlist_adjoin(used_subckts, subckt_name);
}
else if (*line == 'a') {
char *model_name = get_adevice_model_name(line);
char *model_name = get_adevice_model_name(line, card->level);
nlist_adjoin(used_models, model_name);
}
else if (has_models) {
@ -10245,9 +10265,12 @@ void inp_rem_unused_models(struct nscope *root, struct card *deck)
/* num_terminals may be 0 for a elements */
if ((num_terminals != 0) || (*curr_line == 'a')) {
char *elem_model_name;
if (*curr_line == 'a')
elem_model_name = get_adevice_model_name(curr_line);
else
if (*curr_line == 'a'){
elem_model_name = get_adevice_model_name( curr_line, card->level);
if (!elem_model_name){
continue;
}
}else
elem_model_name = get_model_name(curr_line, num_terminals);
/* ignore certain cases, for example
@ -10288,7 +10311,7 @@ void inp_rem_unused_models(struct nscope *root, struct card *deck)
* only correct UTF-8. It also spots UTF-8 sequences that could cause
* trouble if converted to UTF-16, namely surrogate characters
* (U+D800..U+DFFF) and non-Unicode positions (U+FFFE..U+FFFF).
* In addition we check for some ngspice-specific characters like µ etc.*/
* In addition we check for some ngspice-specific characters like <EFBFBD> etc.*/
#ifndef EXT_ASC
static unsigned char*
utf8_check(unsigned char *s)
@ -10298,12 +10321,12 @@ utf8_check(unsigned char *s)
/* 0xxxxxxx */
s++;
else if (*s == 0xb5) {
/* translate ansi micro µ to u */
/* translate ansi micro <EFBFBD> to u */
*s = 'u';
s++;
}
else if (s[0] == 0xc2 && s[1] == 0xb5) {
/* translate utf-8 micro µ to u */
/* translate utf-8 micro <EFBFBD> to u */
s[0] = 'u';
/* remove second byte */
unsigned char *y = s + 1;

View File

@ -299,6 +299,10 @@ struct IFdevice {
#endif
int flags; /* DEV_ */
#ifdef OSDI
const void *registry_entry;
#endif
};

View File

@ -0,0 +1,32 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#pragma once
#include "ngspice/devdefs.h"
#include <stdint.h>
typedef struct OsdiRegistryEntry {
const void *descriptor;
uint32_t inst_offset;
uint32_t dt;
uint32_t temp;
} OsdiRegistryEntry;
typedef struct OsdiObjectFile {
OsdiRegistryEntry *entrys;
int num_entries;
} OsdiObjectFile;
extern OsdiObjectFile load_object_file(const char *path);
extern SPICEdev *osdi_create_spicedev(const OsdiRegistryEntry *entry);
extern char *inputdir;

25
src/osdi/Makefile.am Normal file
View File

@ -0,0 +1,25 @@
## Process this file with automake to produce Makefile.in
noinst_LTLIBRARIES = libosdi.la
libosdi_la_SOURCES = \
osdi.h \
osdidefs.h \
osdiext.h \
osdiinit.c \
osdiload.c \
osdiacld.c \
osdiparam.c \
osdiregistry.c \
osdisetup.c \
osdiitf.h \
osditrunc.c \
osdipzld.c \
osdicallbacks.c
AM_CPPFLAGS = @AM_CPPFLAGS@ -I$(top_srcdir)/src/include
AM_CFLAGS = $(STATIC)
MAINTAINERCLEANFILES = Makefile.in

209
src/osdi/osdi.h Normal file
View File

@ -0,0 +1,209 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* This file is automatically generated by header.lua to match
* the OSDI specfication. DO NOT EDIT MANUALLY
*
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#define OSDI_VERSION_MAJOR_CURR 0
#define OSDI_VERSION_MINOR_CURR 3
#define PARA_TY_MASK 3
#define PARA_TY_REAL 0
#define PARA_TY_INT 1
#define PARA_TY_STR 2
#define PARA_KIND_MASK (3 << 30)
#define PARA_KIND_MODEL (0 << 30)
#define PARA_KIND_INST (1 << 30)
#define PARA_KIND_OPVAR (2 << 30)
#define ACCESS_FLAG_READ 0
#define ACCESS_FLAG_SET 1
#define ACCESS_FLAG_INSTANCE 4
#define JACOBIAN_ENTRY_RESIST_CONST 1
#define JACOBIAN_ENTRY_REACT_CONST 2
#define JACOBIAN_ENTRY_RESIST 4
#define JACOBIAN_ENTRY_REACT 8
#define CALC_RESIST_RESIDUAL 1
#define CALC_REACT_RESIDUAL 2
#define CALC_RESIST_JACOBIAN 4
#define CALC_REACT_JACOBIAN 8
#define CALC_NOISE 16
#define CALC_OP 32
#define CALC_RESIST_LIM_RHS 64
#define CALC_REACT_LIM_RHS 128
#define ENABLE_LIM 256
#define INIT_LIM 512
#define ANALYSIS_NOISE 1024
#define ANALYSIS_DC 2048
#define ANALYSIS_AC 4096
#define ANALYSIS_TRAN 8192
#define ANALYSIS_IC 16384
#define ANALYSIS_STATIC 32768
#define ANALYSIS_NODESET 65536
#define EVAL_RET_FLAG_LIM 1
#define EVAL_RET_FLAG_FATAL 2
#define EVAL_RET_FLAG_FINISH 4
#define EVAL_RET_FLAG_STOP 8
#define LOG_LVL_MASK 8
#define LOG_LVL_DEBUG 0
#define LOG_LVL_DISPLAY 1
#define LOG_LVL_INFO 2
#define LOG_LVL_WARN 3
#define LOG_LVL_ERR 4
#define LOG_LVL_FATAL 5
#define LOG_FMT_ERR 16
#define INIT_ERR_OUT_OF_BOUNDS 1
typedef struct OsdiLimFunction {
char *name;
uint32_t num_args;
void *func_ptr;
}OsdiLimFunction;
typedef struct OsdiSimParas {
char **names;
double *vals;
char **names_str;
char **vals_str;
}OsdiSimParas;
typedef struct OsdiSimInfo {
OsdiSimParas paras;
double abstime;
double *prev_solve;
double *prev_state;
double *next_state;
uint32_t flags;
}OsdiSimInfo;
typedef union OsdiInitErrorPayload {
uint32_t parameter_id;
}OsdiInitErrorPayload;
typedef struct OsdiInitError {
uint32_t code;
OsdiInitErrorPayload payload;
}OsdiInitError;
typedef struct OsdiInitInfo {
uint32_t flags;
uint32_t num_errors;
OsdiInitError *errors;
}OsdiInitInfo;
typedef struct OsdiNodePair {
uint32_t node_1;
uint32_t node_2;
}OsdiNodePair;
typedef struct OsdiJacobianEntry {
OsdiNodePair nodes;
uint32_t react_ptr_off;
uint32_t flags;
}OsdiJacobianEntry;
typedef struct OsdiNode {
char *name;
char *units;
char *residual_units;
uint32_t resist_residual_off;
uint32_t react_residual_off;
uint32_t resist_limit_rhs_off;
uint32_t react_limit_rhs_off;
bool is_flow;
}OsdiNode;
typedef struct OsdiParamOpvar {
char **name;
uint32_t num_alias;
char *description;
char *units;
uint32_t flags;
uint32_t len;
}OsdiParamOpvar;
typedef struct OsdiNoiseSource {
char *name;
OsdiNodePair nodes;
}OsdiNoiseSource;
typedef struct OsdiDescriptor {
char *name;
uint32_t num_nodes;
uint32_t num_terminals;
OsdiNode *nodes;
uint32_t num_jacobian_entries;
OsdiJacobianEntry *jacobian_entries;
uint32_t num_collapsible;
OsdiNodePair *collapsible;
uint32_t collapsed_offset;
OsdiNoiseSource *noise_sources;
uint32_t num_noise_src;
uint32_t num_params;
uint32_t num_instance_params;
uint32_t num_opvars;
OsdiParamOpvar *param_opvar;
uint32_t node_mapping_offset;
uint32_t jacobian_ptr_resist_offset;
uint32_t num_states;
uint32_t state_idx_off;
uint32_t bound_step_offset;
uint32_t instance_size;
uint32_t model_size;
void *(*access)(void *inst, void *model, uint32_t id, uint32_t flags);
void (*setup_model)(void *handle, void *model, OsdiSimParas *sim_params,
OsdiInitInfo *res);
void (*setup_instance)(void *handle, void *inst, void *model,
double temperature, uint32_t num_terminals,
OsdiSimParas *sim_params, OsdiInitInfo *res);
uint32_t (*eval)(void *handle, void *inst, void *model, OsdiSimInfo *info);
void (*load_noise)(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens);
void (*load_residual_resist)(void *inst, void* model, double *dst);
void (*load_residual_react)(void *inst, void* model, double *dst);
void (*load_limit_rhs_resist)(void *inst, void* model, double *dst);
void (*load_limit_rhs_react)(void *inst, void* model, double *dst);
void (*load_spice_rhs_dc)(void *inst, void* model, double *dst,
double* prev_solve);
void (*load_spice_rhs_tran)(void *inst, void* model, double *dst,
double* prev_solve, double alpha);
void (*load_jacobian_resist)(void *inst, void* model);
void (*load_jacobian_react)(void *inst, void* model, double alpha);
void (*load_jacobian_tran)(void *inst, void* model, double alpha);
}OsdiDescriptor;

44
src/osdi/osdiacld.c Normal file
View File

@ -0,0 +1,44 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int OSDIacLoad(GENmodel *inModel, CKTcircuit *ckt) {
GENmodel *gen_model;
GENinstance *gen_inst;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// nothing to calculate just load the matrix entries calculated during
// operating point iterations
descr->load_jacobian_resist(inst, model);
descr->load_jacobian_react(inst, model, ckt->CKTomega);
}
}
return (OK);
}

88
src/osdi/osdicallbacks.c Normal file
View File

@ -0,0 +1,88 @@
#include "ngspice/devdefs.h"
#include "osdidefs.h"
void osdi_log(void *handle_, char *msg, uint32_t lvl) {
OsdiNgspiceHandle *handle = handle_;
FILE *dst = stdout;
switch (lvl & LOG_LVL_MASK) {
case LOG_LVL_DEBUG:
printf("OSDI(debug) %s: ", handle->name);
break;
case LOG_LVL_DISPLAY:
printf("OSDI %s: ", handle->name);
break;
case LOG_LVL_INFO:
printf("OSDI(info) %s: ", handle->name);
break;
case LOG_LVL_WARN:
fprintf(stderr, "OSDI(warn) %s: ", handle->name);
dst = stderr;
break;
case LOG_LVL_ERR:
fprintf(stderr, "OSDI(err) %s: ", handle->name);
dst = stderr;
break;
case LOG_LVL_FATAL:
fprintf(stderr, "OSDI(fatal) %s: ", handle->name);
dst = stderr;
break;
default:
fprintf(stderr, "OSDI(unkown) %s", handle->name);
break;
}
if (lvl & LOG_FMT_ERR) {
fprintf(dst, "failed to format\"%s\"\n", msg);
} else {
fprintf(dst, "%s", msg);
}
}
double osdi_pnjlim(bool init, bool *check, double vnew, double vold, double vt,
double vcrit) {
if (init) {
*check = true;
return vcrit;
}
int icheck = 0;
double res = DEVpnjlim(vnew, vold, vt, vcrit, &icheck);
*check = icheck != 0;
return res;
}
double osdi_limvds(bool init, bool *check, double vnew, double vold) {
if (init) {
*check = true;
return 0.1;
}
double res = DEVlimvds(vnew, vold);
if (res != vnew) {
*check = true;
}
return res;
}
double osdi_fetlim(bool init, bool *check, double vnew, double vold,
double vto) {
if (init) {
*check = true;
return vto + 0.1;
}
double res = DEVfetlim(vnew, vold, vto);
if (res != vnew) {
*check = true;
}
return res;
}
double osdi_limitlog(bool init, bool *check, double vnew, double vold,
double LIM_TOL) {
if (init) {
*check = true;
return 0.0;
}
int icheck = 0;
double res = DEVlimitlog(vnew, vold, LIM_TOL, &icheck);
*check = icheck != 0;
return res;
}

74
src/osdi/osdidefs.h Normal file
View File

@ -0,0 +1,74 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#pragma once
#include "ngspice/cktdefs.h"
#include "ngspice/complex.h"
#include "ngspice/fteext.h"
#include "ngspice/gendefs.h"
#include "ngspice/ifsim.h"
#include "ngspice/ngspice.h"
#include "ngspice/noisedef.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdiext.h"
#include "stddef.h"
#include <stddef.h>
#include <stdint.h>
typedef struct OsdiModelData {
GENmodel gen;
max_align_t data;
} OsdiModelData;
typedef struct OsdiExtraInstData {
double dt;
double temp;
bool temp_given;
bool dt_given;
bool finish;
} __attribute__((aligned(sizeof(max_align_t)))) OsdiExtraInstData;
size_t osdi_instance_data_off(const OsdiRegistryEntry *entry);
void *osdi_instance_data(const OsdiRegistryEntry *entry, GENinstance *inst);
OsdiExtraInstData *osdi_extra_instance_data(const OsdiRegistryEntry *entry,
GENinstance *inst);
size_t osdi_model_data_off(void);
void *osdi_model_data(GENmodel *model);
void *osdi_model_data_from_inst(GENinstance *inst);
OsdiRegistryEntry *osdi_reg_entry_model(const GENmodel *model);
OsdiRegistryEntry *osdi_reg_entry_inst(const GENinstance *inst);
typedef struct OsdiNgspiceHandle {
uint32_t kind;
char *name;
} OsdiNgspiceHandle;
/* values returned by $simparam*/
OsdiSimParas get_simparams(const CKTcircuit *ckt);
typedef void (*osdi_log_ptr)(void *handle, char *msg, uint32_t lvl);
void osdi_log(void *handle_, char *msg, uint32_t lvl);
typedef void (*osdi_log_ptr)(void *handle, char *msg, uint32_t lvl);
double osdi_pnjlim(bool init, bool *icheck, double vnew, double vold, double vt,
double vcrit);
double osdi_limvds(bool init, bool *icheck, double vnew, double vold);
double osdi_limitlog(bool init, bool *icheck, double vnew, double vold,
double LIM_TOL);
double osdi_fetlim(bool init, bool *icheck, double vnew, double vold,
double vto);

36
src/osdi/osdiext.h Normal file
View File

@ -0,0 +1,36 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#pragma once
#include "ngspice/gendefs.h"
#include "ngspice/smpdefs.h"
#include <stdint.h>
#include "ngspice/osdiitf.h"
extern int OSDImParam(int, IFvalue *, GENmodel *);
extern int OSDIparam(int, IFvalue *, GENinstance *, IFvalue *);
extern int OSDIsetup(SMPmatrix *, GENmodel *, CKTcircuit *, int *);
extern int OSDIunsetup(GENmodel *, CKTcircuit *);
extern int OSDIask(CKTcircuit *, GENinstance *, int, IFvalue *, IFvalue *);
extern int OSDIload(GENmodel *, CKTcircuit *);
extern int OSDItemp(GENmodel *, CKTcircuit *);
extern int OSDIacLoad(GENmodel *, CKTcircuit *);
extern int OSDItrunc(GENmodel *, CKTcircuit *, double *);
extern int OSDIpzLoad(GENmodel*, CKTcircuit*, SPcomplex*);
/* extern int OSDIconvTest(GENmodel*,CKTcircuit*); */
/* extern int OSDImDelete(GENmodel*); */
/* extern int OSDIgetic(GENmodel*,CKTcircuit*); */
/* extern int OSDImAsk(CKTcircuit*,GENmodel*,int,IFvalue*); */
/* extern int OSDInoise(int,int,GENmodel*,CKTcircuit*,Ndata*,double*); */
/* extern int OSDIsoaCheck(CKTcircuit *, GENmodel *); */

187
src/osdi/osdiinit.c Normal file
View File

@ -0,0 +1,187 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/stringutil.h"
#include "ngspice/config.h"
#include "ngspice/devdefs.h"
#include "ngspice/iferrmsg.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/*
* This function converts the information in (a list of) OsdiParamOpvar in
* descr->param_opvar to the internal ngspice representation (IFparm).
*/
static int write_param_info(IFparm **dst, const OsdiDescriptor *descr,
uint32_t start, uint32_t end) {
for (uint32_t i = start; i < end; i++) {
OsdiParamOpvar *para = &descr->param_opvar[i];
uint32_t num_names = para->num_alias + 1;
int dataType = IF_ASK;
if ((para->flags & (uint32_t)PARA_KIND_OPVAR) == 0) {
dataType |= IF_SET;
}
switch (para->flags & PARA_TY_MASK) {
case PARA_TY_REAL:
dataType |= IF_REAL;
break;
case PARA_TY_INT:
dataType |= IF_INTEGER;
break;
case PARA_TY_STR:
dataType |= IF_STRING;
break;
default:
errRtn = "get_osdi_info";
errMsg = tprintf("Unkown OSDI type %d for parameter %s!",
para->flags & PARA_TY_MASK, para->name[0]);
return -1;
}
if (para->len != 0) {
dataType |= IF_VECTOR;
}
for (uint32_t j = 0; j < num_names; j++) {
if (j != 0) {
dataType = IF_UNINTERESTING;
}
char *para_name = copy(para->name[j]);
strtolower(para_name);
(*dst)[j] = (IFparm){.keyword = para_name,
.id = (int)i,
.description = para->description,
.dataType = dataType};
}
*dst += num_names;
}
return 0;
}
/**
* This function creates a SPICEdev instance for a specific OsdiDescriptor by
* populating the SPICEdev struct with descriptor specific metadata and pointers
* to the descriptor independent functions.
* */
extern SPICEdev *osdi_create_spicedev(const OsdiRegistryEntry *entry) {
const OsdiDescriptor *descr = entry->descriptor;
// allocate and fill terminal names array
char **termNames = TMALLOC(char *, descr->num_terminals);
for (uint32_t i = 0; i < descr->num_terminals; i++) {
termNames[i] = descr->nodes[i].name;
}
// allocate and fill instance params (and opvars)
int *num_instance_para_names = TMALLOC(int, 1);
for (uint32_t i = 0; i < descr->num_instance_params; i++) {
*num_instance_para_names += (int)(1 + descr->param_opvar[i].num_alias);
}
for (uint32_t i = descr->num_params;
i < descr->num_opvars + descr->num_params; i++) {
*num_instance_para_names += (int)(1 + descr->param_opvar[i].num_alias);
}
if (entry->dt != UINT32_MAX) {
*num_instance_para_names += 1;
}
if (entry->temp != UINT32_MAX) {
*num_instance_para_names += 1;
}
IFparm *instance_para_names = TMALLOC(IFparm, *num_instance_para_names);
IFparm *dst = instance_para_names;
if (entry->dt != UINT32_MAX) {
dst[0] = (IFparm){"dt", (int)entry->dt, IF_REAL | IF_SET,
"Instance delta temperature"};
dst += 1;
}
if (entry->temp != UINT32_MAX) {
dst[0] = (IFparm){"temp", (int)entry->temp, IF_REAL | IF_SET,
"Instance temperature"};
dst += 1;
}
write_param_info(&dst, descr, 0, descr->num_instance_params);
write_param_info(&dst, descr, descr->num_params,
descr->num_params + descr->num_opvars);
// allocate and fill model params
int *num_model_para_names = TMALLOC(int, 1);
for (uint32_t i = descr->num_instance_params; i < descr->num_params; i++) {
*num_model_para_names += (int)(1 + descr->param_opvar[i].num_alias);
}
IFparm *model_para_names = TMALLOC(IFparm, *num_model_para_names);
dst = model_para_names;
write_param_info(&dst, descr, descr->num_instance_params, descr->num_params);
// Allocate SPICE device
SPICEdev *OSDIinfo = TMALLOC(SPICEdev, 1);
// fill information
OSDIinfo->DEVpublic = (IFdevice){
.name = descr->name,
.description = "A simulator independent device loaded with OSDI",
// TODO why extra indirection? Optional ports?
.terms = (int *)&descr->num_terminals,
.numNames = (int *)&descr->num_terminals,
.termNames = termNames,
.numInstanceParms = num_instance_para_names,
.instanceParms = instance_para_names,
.numModelParms = num_model_para_names,
.modelParms = model_para_names,
.flags = DEV_DEFAULT,
.registry_entry = (void *)entry,
};
size_t inst_off = entry->inst_offset;
int *inst_size = TMALLOC(int, 1);
*inst_size =
(int)(inst_off + descr->instance_size + sizeof(OsdiExtraInstData));
OSDIinfo->DEVinstSize = inst_size;
size_t model_off = osdi_model_data_off();
int *model_size = TMALLOC(int, 1);
*model_size = (int)(model_off + descr->model_size);
OSDIinfo->DEVmodSize = model_size;
// fill generic functions
OSDIinfo->DEVparam = OSDIparam;
OSDIinfo->DEVmodParam = OSDImParam;
OSDIinfo->DEVask = OSDIask;
OSDIinfo->DEVsetup = OSDIsetup;
OSDIinfo->DEVpzSetup = OSDIsetup;
OSDIinfo->DEVtemperature = OSDItemp;
OSDIinfo->DEVunsetup = OSDIunsetup;
OSDIinfo->DEVload = OSDIload;
OSDIinfo->DEVacLoad = OSDIacLoad;
OSDIinfo->DEVpzLoad = OSDIpzLoad;
OSDIinfo->DEVtrunc = OSDItrunc;
return OSDIinfo;
}

211
src/osdi/osdiload.c Normal file
View File

@ -0,0 +1,211 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#define NUM_SIM_PARAMS 5
char *sim_params[NUM_SIM_PARAMS + 1] = {
"gdev", "gmin", "tnom", "simulatorVersion", "sourceScaleFactor", NULL};
char *sim_params_str[1] = {NULL};
double sim_param_vals[NUM_SIM_PARAMS] = {};
/* values returned by $simparam*/
OsdiSimParas get_simparams(const CKTcircuit *ckt) {
double simulatorVersion = strtod(PACKAGE_VERSION, NULL);
double gdev = ckt->CKTgmin;
double sourceScaleFactor = ckt->CKTsrcFact;
double gmin = ((ckt->CKTgmin) > (ckt->CKTdiagGmin)) ? (ckt->CKTgmin)
: (ckt->CKTdiagGmin);
double sim_param_vals_[NUM_SIM_PARAMS] = {
gdev, gmin, ckt->CKTnomTemp, simulatorVersion, sourceScaleFactor};
memcpy(&sim_param_vals, &sim_param_vals_, sizeof(double) * NUM_SIM_PARAMS);
OsdiSimParas sim_params_ = {.names = sim_params,
.vals = (double *)&sim_param_vals,
.names_str = sim_params_str,
.vals_str = NULL};
return sim_params_;
}
extern int OSDIload(GENmodel *inModel, CKTcircuit *ckt) {
OsdiNgspiceHandle handle;
GENmodel *gen_model;
GENinstance *gen_inst;
double dump;
bool is_init_smsig = ckt->CKTmode & MODEINITSMSIG;
bool is_sweep = ckt->CKTmode & MODEDCTRANCURVE;
bool is_dc = ckt->CKTmode & (MODEDCOP | MODEDCTRANCURVE);
bool is_ac = ckt->CKTmode & (MODEAC | MODEINITSMSIG);
bool is_tran = ckt->CKTmode & (MODETRAN);
bool is_tran_op = ckt->CKTmode & (MODETRANOP);
bool is_init_tran = ckt->CKTmode & MODEINITTRAN;
bool is_init_junc = ckt->CKTmode & MODEINITJCT;
OsdiSimInfo sim_info = {
.paras = get_simparams(ckt),
.abstime = is_tran ? ckt->CKTtime : 0.0,
.prev_solve = ckt->CKTrhsOld,
.prev_state = ckt->CKTstates[0],
.next_state = ckt->CKTstates[0],
.flags = CALC_RESIST_JACOBIAN,
};
if (is_init_smsig || is_sweep) {
sim_info.flags |= CALC_OP;
}
if (is_dc) {
sim_info.flags |= ANALYSIS_DC | ANALYSIS_STATIC;
}
if (!is_init_smsig) {
sim_info.flags |= CALC_RESIST_RESIDUAL | ENABLE_LIM | CALC_RESIST_LIM_RHS;
}
if (is_tran) {
sim_info.flags |= CALC_REACT_JACOBIAN | CALC_REACT_RESIDUAL |
CALC_REACT_LIM_RHS | ANALYSIS_TRAN;
}
if (is_tran_op) {
sim_info.flags |= ANALYSIS_TRAN;
}
if (is_ac) {
sim_info.flags |= CALC_REACT_JACOBIAN | ANALYSIS_AC;
}
if (is_init_tran) {
sim_info.flags |= ANALYSIS_IC | ANALYSIS_STATIC;
}
if (is_init_junc) {
sim_info.flags |= INIT_LIM;
}
if (ckt->CKTmode & MODEACNOISE) {
sim_info.flags |= CALC_NOISE | ANALYSIS_NOISE;
}
int ret = OK;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
/* hpyothetically this could run in parallel we do not write any shared
data here*/
handle = (OsdiNgspiceHandle){.kind = 3, .name = gen_inst->GENname};
/* TODO initial conditions? */
uint32_t ret_flags = descr->eval(&handle, inst, model, &sim_info);
/* call to $fatal in Verilog-A abort!*/
if (ret_flags & EVAL_RET_FLAG_FATAL) {
return E_PANIC;
}
/* init small signal analysis does not require loading values into
* matrix/rhs*/
if (is_init_smsig) {
continue;
}
/* handle calls to $finish, $limit, $stop
* TODO actually do something with extra_inst_data->finish and
* extra_inst_data->limt
* */
OsdiExtraInstData *extra_inst_data =
osdi_extra_instance_data(entry, gen_inst);
if (ret_flags & EVAL_RET_FLAG_FINISH) {
extra_inst_data->finish = true;
}
if (ret_flags & EVAL_RET_FLAG_LIM) {
ckt->CKTnoncon++;
ckt->CKTtroubleElt = gen_inst;
}
if (ret_flags & EVAL_RET_FLAG_STOP) {
ret = (E_PAUSE);
}
if (is_tran) {
/* load dc matrix and capacitances (charge derivative multiplied with
* CKTag[0]) */
descr->load_jacobian_tran(inst, model, ckt->CKTag[0]);
/* load static rhs and dynamic linearized rhs (SUM Vb * dIa/dVb)*/
descr->load_spice_rhs_tran(inst, model, ckt->CKTrhs, ckt->CKTrhsOld,
ckt->CKTag[0]);
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
/* use numeric integration to obtain the remainer of the RHS*/
int state = gen_inst->GENstate + (int) descr->num_states;
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (descr->nodes[i].react_residual_off != UINT32_MAX) {
double residual_react = *((
double *)(((char *)inst) + descr->nodes[i].react_residual_off));
/* store charges in state vector*/
ckt->CKTstate0[state] = residual_react;
if (is_init_tran) {
ckt->CKTstate1[state] = residual_react;
}
/* we only care about the numeric integration itself not ceq/geq
because those are already calculated by load_jacobian_tran and
load_spice_rhs_tran*/
NIintegrate(ckt, &dump, &dump, 0, state);
/* add the numeric derivative to the rhs */
ckt->CKTrhs[node_mapping[i]] -= ckt->CKTstate0[state + 1];
if (is_init_tran) {
ckt->CKTstate1[state + 1] = ckt->CKTstate0[state + 1];
}
state += 2;
}
}
} else {
/* copy internal derivatives into global matrix */
descr->load_jacobian_resist(inst, model);
/* calculate spice RHS from internal currents and store into global RHS
*/
descr->load_spice_rhs_dc(inst, model, ckt->CKTrhs, ckt->CKTrhsOld);
}
}
}
return ret;
}

165
src/osdi/osdiparam.c Normal file
View File

@ -0,0 +1,165 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdidefs.h"
#include <stdint.h>
#include <string.h>
static int osdi_param_access(OsdiParamOpvar *param_info, bool write_value,
IFvalue *value, void *ptr) {
size_t len;
void *val_ptr;
switch (param_info->flags & PARA_TY_MASK) {
case PARA_TY_REAL:
len = sizeof(double);
if (param_info->len) {
len *= param_info->len;
val_ptr = &value->v.vec.rVec;
} else {
val_ptr = &value->rValue;
}
break;
case PARA_TY_INT:
len = sizeof(int);
if (param_info->len) {
len *= param_info->len;
val_ptr = &value->v.vec.iVec;
} else {
val_ptr = &value->iValue;
}
break;
case PARA_TY_STR:
len = sizeof(char *);
if (param_info->len) {
len *= param_info->len;
val_ptr = &value->v.vec.cVec;
} else {
val_ptr = &value->cValue;
}
break;
default:
return (E_PARMVAL);
}
if (write_value) {
memcpy(val_ptr, ptr, len);
} else {
memcpy(ptr, val_ptr, len);
}
return OK;
}
static int osdi_write_param(void *dst, IFvalue *value, int param,
const OsdiDescriptor *descr) {
if (dst == NULL) {
return (E_PANIC);
}
OsdiParamOpvar *param_info = &descr->param_opvar[param];
if (param_info->len) {
if ((uint32_t)value->v.numValue != param_info->len) {
return (E_PARMVAL);
}
}
return osdi_param_access(param_info, false, value, dst);
}
extern int OSDIparam(int param, IFvalue *value, GENinstance *instPtr,
IFvalue *select) {
NG_IGNORE(select);
OsdiRegistryEntry *entry = osdi_reg_entry_inst(instPtr);
const OsdiDescriptor *descr = entry->descriptor;
if (param > (int)descr->num_instance_params) {
// special handleing for temperature parameters
OsdiExtraInstData *inst = osdi_extra_instance_data(entry, instPtr);
if (param == (int)entry->dt) {
inst->dt = value->rValue;
inst->dt_given = true;
return (OK);
}
if (param == (int)entry->temp) {
inst->temp = value->rValue;
inst->temp_given = true;
return (OK);
}
return (E_BADPARM);
}
void *inst = osdi_instance_data(entry, instPtr);
void *dst = descr->access(inst, NULL, (uint32_t)param,
ACCESS_FLAG_SET | ACCESS_FLAG_INSTANCE);
return osdi_write_param(dst, value, param, descr);
}
extern int OSDImParam(int param, IFvalue *value, GENmodel *modelPtr) {
OsdiRegistryEntry *entry = osdi_reg_entry_model(modelPtr);
const OsdiDescriptor *descr = entry->descriptor;
if (param > (int)descr->num_params ||
param < (int)descr->num_instance_params) {
return (E_BADPARM);
}
void *model = osdi_model_data(modelPtr);
void *dst = descr->access(NULL, model, (uint32_t)param, ACCESS_FLAG_SET);
return osdi_write_param(dst, value, param, descr);
}
static int osdi_read_param(void *src, IFvalue *value, int id,
const OsdiDescriptor *descr) {
if (src == NULL) {
return (E_PANIC);
}
OsdiParamOpvar *param_info = &descr->param_opvar[id];
if (param_info->len) {
value->v.numValue = (int)param_info->len;
}
return osdi_param_access(param_info, true, value, src);
}
extern int OSDIask(CKTcircuit *ckt, GENinstance *instPtr, int id,
IFvalue *value, IFvalue *select) {
NG_IGNORE(select);
NG_IGNORE(ckt);
OsdiRegistryEntry *entry = osdi_reg_entry_inst(instPtr);
void *inst = osdi_instance_data(entry, instPtr);
void *model = osdi_model_data_from_inst(instPtr);
const OsdiDescriptor *descr = entry->descriptor;
if (id > (int)(descr->num_params + descr->num_instance_params)) {
return (E_BADPARM);
}
uint32_t flags = ACCESS_FLAG_READ;
if (id < (int)descr->num_instance_params) {
flags |= ACCESS_FLAG_INSTANCE;
}
void *src = descr->access(inst, model, (uint32_t)id, flags);
return osdi_read_param(src, value, id, descr);
}

49
src/osdi/osdipzld.c Normal file
View File

@ -0,0 +1,49 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int OSDIpzLoad(GENmodel *inModel, CKTcircuit *ckt, SPcomplex *s) {
NG_IGNORE(ckt);
GENmodel *gen_model;
GENinstance *gen_inst;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// nothing to calculate just load the matrix entries calculated during
// operating point iterations
// the load_jacobian_tran function migh seem weird here but all this does
// is adding J_resist + J_react * a to every matrix entry (real part).
// J_resist are the conductances (normal matrix entries) and J_react the
// capcitances
descr->load_jacobian_tran(inst, model, s->real);
descr->load_jacobian_react(inst, model, s->imag);
}
}
return (OK);
}

427
src/osdi/osdiregistry.c Normal file
View File

@ -0,0 +1,427 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/hash.h"
#include "ngspice/memory.h"
#include "ngspice/stringutil.h"
#include "osdidefs.h"
#include <sys/stat.h>
#include "osdi.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if (!defined HAS_WINGUI) && (!defined __MINGW32__) && (!defined _MSC_VER)
#include <dlfcn.h> /* to load libraries*/
#define OPENLIB(path) dlopen(path, RTLD_NOW | RTLD_LOCAL)
#define GET_SYM(lib, sym) dlsym(lib, sym)
#define FREE_DLERR_MSG(msg)
#else /* ifdef HAS_WINGUI */
#undef BOOLEAN
#include <windows.h>
#define OPENLIB(path) LoadLibrary(path)
#define GET_SYM(lib, sym) ((void *)GetProcAddress(lib, sym))
char *dlerror(void);
#define FREE_DLERR_MSG(msg) free_dlerr_msg(msg)
static void free_dlerr_msg(char *msg);
#endif /* ifndef HAS_WINGUI */
char *inputdir = NULL;
/* Returns true if path is an absolute path and false if it is a
* relative path. No check is done for the existance of the path. */
inline static bool is_absolute_pathname(const char *path) {
#ifdef _WIN32
return !PathIsRelativeA(path);
#else
return path[0] == DIR_TERM;
#endif
} /* end of funciton is_absolute_pathname */
/*-------------------------------------------------------------------------*
Look up the variable sourcepath and try everything in the list in order
if the file isn't in . and it isn't an abs path name.
*-------------------------------------------------------------------------*/
static char *resolve_path(const char *name) {
struct stat st;
#if defined(_WIN32)
/* If variable 'mingwpath' is set: convert mingw /d/... to d:/... */
if (cp_getvar("mingwpath", CP_BOOL, NULL, 0) && name[0] == DIR_TERM_LINUX &&
isalpha_c(name[1]) && name[2] == DIR_TERM_LINUX) {
DS_CREATE(ds, 100);
if (ds_cat_str(&ds, name) != 0) {
fprintf(stderr, "Unable to copy string while resolving path");
controlled_exit(EXIT_FAILURE);
}
char *const buf = ds_get_buf(&ds);
buf[0] = buf[1];
buf[1] = ':';
char *const resolved_path = resolve_path(buf);
ds_free(&ds);
return resolved_path;
}
#endif
/* just try it */
if (stat(name, &st) == 0)
return copy(name);
#if !defined(EXT_ASC) && (defined(__MINGW32__) || defined(_MSC_VER))
wchar_t wname[BSIZE_SP];
if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname,
2 * (int)strlen(name) + 1) == 0) {
fprintf(stderr, "UTF-8 to UTF-16 conversion failed with 0x%x\n",
GetLastError());
fprintf(stderr, "%s could not be converted\n", name);
return NULL;
}
if (_waccess(wname, 0) == 0)
return copy(name);
#endif
return (char *)NULL;
}
static char *resolve_input_path(const char *name) {
/* if name is an absolute path name,
* or if we haven't anything to prepend anyway
*/
if (is_absolute_pathname(name)) {
return resolve_path(name);
}
if (name[0] == '~' && name[1] == '/') {
char *const y = cp_tildexpand(name);
if (y) {
char *const r = resolve_path(y);
txfree(y);
return r;
}
}
/*
* If called from a script inputdir != NULL so try relativ to that dir
* Otherwise try relativ to the current workdir
*/
if (inputdir) {
DS_CREATE(ds, 100);
int rc_ds = 0;
rc_ds |= ds_cat_str(&ds, inputdir); /* copy the dir name */
const size_t n = ds_get_length(&ds); /* end of copied dir name */
/* Append a directory separator if not present already */
const char ch_last = n > 0 ? inputdir[n - 1] : '\0';
if (ch_last != DIR_TERM
#ifdef _WIN32
&& ch_last != DIR_TERM_LINUX
#endif
) {
rc_ds |= ds_cat_char(&ds, DIR_TERM);
}
rc_ds |= ds_cat_str(&ds, name); /* append the file name */
if (rc_ds != 0) {
(void)fprintf(cp_err, "Unable to build \"dir\" path name "
"in inp_pathresolve_at");
controlled_exit(EXIT_FAILURE);
}
char *const r = resolve_path(ds_get_buf(&ds));
ds_free(&ds);
return r;
} else {
DS_CREATE(ds, 100);
if (ds_cat_printf(&ds, ".%c%s", DIR_TERM, name) != 0) {
(void)fprintf(cp_err,
"Unable to build \".\" path name in inp_pathresolve_at");
controlled_exit(EXIT_FAILURE);
}
char *const r = resolve_path(ds_get_buf(&ds));
ds_free(&ds);
if (r != (char *)NULL) {
return r;
}
}
return NULL;
} /* end of function inp_pathresolve_at */
/**
* Calculates the offset that the OSDI instance data has from the beginning of
* the instance data allocated by ngspice. This offset is non trivial because
* ngspice must store the terminal pointers before the remaining instance
* data. As a result the offset is not constant and a variable amount of
* padding must be inserted to ensure correct alginment.
*/
static size_t calc_osdi_instance_data_off(const OsdiDescriptor *descr) {
size_t res = sizeof(GENinstance) /* generic data */
+ descr->num_terminals * sizeof(int);
size_t padding = sizeof(max_align_t) - res % sizeof(max_align_t);
if (padding == sizeof(max_align_t)) {
padding = 0;
}
return res + padding;
}
#define INVALID_OBJECT \
(OsdiObjectFile) { .num_entries = -1 }
#define EMPTY_OBJECT \
(OsdiObjectFile) {}
#define ERR_AND_RET \
error = dlerror(); \
printf("Error opening osdi lib \"%s\": %s\n", path, error); \
FREE_DLERR_MSG(error); \
return INVALID_OBJECT;
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define GET_CONST(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (!sym) { \
ERR_AND_RET \
} \
const ty name = *((ty *)sym);
#define GET_PTR(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (!sym) { \
ERR_AND_RET \
} \
const ty *name = (ty *)sym;
#define INIT_CALLBACK(name, ty) \
sym = GET_SYM(handle, STRINGIFY(name)); \
if (sym) { \
ty *slot = (ty *)sym; \
*slot = name; \
}
#define IS_LIM_FUN(fun_name, num_args_, val) \
if (strcmp(lim_table[i].name, fun_name) == 0) { \
if (lim_table[i].num_args == num_args_) { \
lim_table[i].func_ptr = (void *)val; \
continue; \
} else { \
expected_args = num_args_; \
} \
}
static NGHASHPTR known_object_files = NULL;
#define DUMMYDATA ((void *)42)
/**
* Loads an object file from the hard drive with the platform equivalent of
* dlopen. This function checks that the OSDI version of the object file is
* valid and then writes all data into the `registry`.
* If any errors occur an appropriate message is written to errMsg
*
* @param PATH path A path to the shared object file
* @param uint32_t* len The amount of entries already written into `registry`
* @param uint32_t* capacity The amount of space available in `registry`
* before reallocation is required
* @returns -1 on error, 0 otherwise
*/
extern OsdiObjectFile load_object_file(const char *input) {
void *handle;
char *error;
const void *sym;
/* ensure the hashtable exists */
if (!known_object_files) {
known_object_files = nghash_init_pointer(8);
}
const char *path = resolve_input_path(input);
if (!path) {
printf("Error opening osdi lib \"%s\": No such file or directory!\n",
input);
return INVALID_OBJECT;
}
handle = OPENLIB(path);
if (!handle) {
ERR_AND_RET
}
/* Keep track of loaded shared object files to avoid loading the same model
* multiple times. We use the handle as a key because the same SO will always
* return the SAME pointer as long as dlclose is not called.
* nghash_insert returns NULL if the key (handle) was not already in the table
* and the data (DUMMYDATA) that was previously insered (!= NULL) otherwise*/
if (nghash_insert(known_object_files, handle, DUMMYDATA)) {
return EMPTY_OBJECT;
}
GET_CONST(OSDI_VERSION_MAJOR, uint32_t);
GET_CONST(OSDI_VERSION_MINOR, uint32_t);
if (OSDI_VERSION_MAJOR != OSDI_VERSION_MAJOR_CURR ||
OSDI_VERSION_MINOR != OSDI_VERSION_MINOR_CURR) {
printf("NGSPICE only supports OSDI v%d.%d but \"%s\" targets v%d.%d!",
OSDI_VERSION_MAJOR_CURR, OSDI_VERSION_MINOR_CURR, path,
OSDI_VERSION_MAJOR, OSDI_VERSION_MINOR);
return INVALID_OBJECT;
}
GET_CONST(OSDI_NUM_DESCRIPTORS, uint32_t);
GET_PTR(OSDI_DESCRIPTORS, OsdiDescriptor);
INIT_CALLBACK(osdi_log, osdi_log_ptr)
uint32_t lim_table_len = 0;
sym = GET_SYM(handle, "OSDI_LIM_TABLE_LEN");
if (sym) {
lim_table_len = *((uint32_t *)sym);
}
sym = GET_SYM(handle, "OSDI_LIM_TABLE");
OsdiLimFunction *lim_table = NULL;
if (sym) {
lim_table = (OsdiLimFunction *)sym;
} else {
lim_table_len = 0;
}
for (uint32_t i = 0; i < lim_table_len; i++) {
int expected_args = -1;
IS_LIM_FUN("pnjlim", 2, osdi_pnjlim)
IS_LIM_FUN("limvds", 0, osdi_limvds)
IS_LIM_FUN("fetlim", 1, osdi_fetlim)
IS_LIM_FUN("limitlog", 1, osdi_limitlog)
if (expected_args == -1) {
printf("warning(osdi): unkown $limit function \"%s\"", lim_table[i].name);
} else {
printf("warning(osdi): unexpected number of arguments %i (expected %i) "
"for \"%s\", ignoring...",
lim_table[i].num_args, expected_args, lim_table[i].name);
}
}
OsdiRegistryEntry *dst = TMALLOC(OsdiRegistryEntry, OSDI_NUM_DESCRIPTORS);
for (uint32_t i = 0; i < OSDI_NUM_DESCRIPTORS; i++) {
const OsdiDescriptor *descr = &OSDI_DESCRIPTORS[i];
uint32_t dt = descr->num_params + descr->num_opvars;
uint32_t temp = descr->num_params + descr->num_opvars + 1;
for (uint32_t param_id = 0; param_id < descr->num_params; param_id++) {
OsdiParamOpvar *param = &descr->param_opvar[param_id];
for (uint32_t j = 0; j < 1 + param->num_alias; j++) {
char *name = param->name[j];
if (!strcmp(name, "dt")) {
dt = UINT32_MAX;
} else if (!strcasecmp(name, "dtemp") || !strcasecmp(name, "dt")) {
dt = param_id;
} else if (!strcmp(name, "temp")) {
temp = UINT32_MAX;
} else if (!strcasecmp(name, "temp") ||
!strcasecmp(name, "temperature")) {
temp = param_id;
}
}
}
size_t inst_off = calc_osdi_instance_data_off(descr);
dst[i] = (OsdiRegistryEntry){
.descriptor = descr,
.inst_offset = (uint32_t)inst_off,
.dt = dt,
.temp = temp,
};
}
return (OsdiObjectFile){
.entrys = dst,
.num_entries = (int)OSDI_NUM_DESCRIPTORS,
};
}
inline size_t osdi_instance_data_off(const OsdiRegistryEntry *entry) {
return entry->inst_offset;
}
inline void *osdi_instance_data(const OsdiRegistryEntry *entry,
GENinstance *inst) {
return (void *)(((char *)inst) + osdi_instance_data_off(entry));
}
inline OsdiExtraInstData *
osdi_extra_instance_data(const OsdiRegistryEntry *entry, GENinstance *inst) {
OsdiDescriptor *descr = (OsdiDescriptor *)entry->descriptor;
return (OsdiExtraInstData *)(((char *)inst) + entry->inst_offset +
descr->instance_size);
}
inline size_t osdi_model_data_off(void) {
return offsetof(OsdiModelData, data);
}
inline void *osdi_model_data(GENmodel *model) {
return (void *)&((OsdiModelData *)model)->data;
}
inline void *osdi_model_data_from_inst(GENinstance *inst) {
return osdi_model_data(inst->GENmodPtr);
}
inline OsdiRegistryEntry *osdi_reg_entry_model(const GENmodel *model) {
return (OsdiRegistryEntry *)ft_sim->devices[model->GENmodType]
->registry_entry;
}
inline OsdiRegistryEntry *osdi_reg_entry_inst(const GENinstance *inst) {
return osdi_reg_entry_model(inst->GENmodPtr);
}
#if defined(__MINGW32__) || defined(HAS_WINGUI) || defined(_MSC_VER)
/* For reporting error message if formatting fails */
static const char errstr_fmt[] =
"Unable to find message in dlerr(). System code = %lu";
static char errstr[sizeof errstr_fmt - 3 + 3 * sizeof(unsigned long)];
char *dlerror(void) {
LPVOID lpMsgBuf;
DWORD rc = FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
if (rc == 0) { /* FormatMessage failed */
(void)sprintf(errstr, errstr_fmt, (unsigned long)GetLastError());
return errstr;
}
return lpMsgBuf; /* Return the formatted message */
} /* end of function dlerror */
/* Free message related to dynamic loading */
static void free_dlerr_msg(char *msg) {
if (msg != errstr) { /* msg is an allocation */
LocalFree(msg);
}
} /* end of function free_dlerr_msg */
#endif /* Windows emulation of dlerr */

410
src/osdi/osdisetup.c Normal file
View File

@ -0,0 +1,410 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/iferrmsg.h"
#include "ngspice/memory.h"
#include "ngspice/ngspice.h"
#include "ngspice/typedefs.h"
#include "osdi.h"
#include "osdidefs.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/*
* Handles any errors raised by the setup_instance and setup_model functions
*/
static int handle_init_info(OsdiInitInfo info, const OsdiDescriptor *descr) {
if (info.flags & (EVAL_RET_FLAG_FATAL | EVAL_RET_FLAG_FINISH)) {
return (E_PANIC);
}
if (info.num_errors == 0) {
return (OK);
}
for (uint32_t i = 0; i < info.num_errors; i++) {
OsdiInitError *err = &info.errors[i];
switch (err->code) {
case INIT_ERR_OUT_OF_BOUNDS: {
char *param = descr->param_opvar[err->payload.parameter_id].name[0];
printf("Parameter %s is out of bounds!\n", param);
break;
}
default:
printf("Unkown OSDO init error code %d!\n", err->code);
}
}
free(info.errors);
errMsg = tprintf("%i errors occurred during initalization", info.num_errors);
return (E_PRIVATE);
}
/*
* The OSDI instance data contains the `node_mapping` array.
* Here an index is stored for each node. This function initalizes this array
* with its indecies {0, 1, 2, 3, .., n}.
* The node collapsing information generated by setup_instance is used to
* replace these initial indecies with those that a node is collapsed into.
* For example collapsing nodes i and j sets node_mapping[i] = j.
*
* Terminals can never be collapsed in ngspice because they are allocated by
* ngspice instead of OSDI. Therefore any node collapsing that involves nodes
* `i < connected_terminals` is ignored.
*
* @param const OsdiDescriptor *descr The OSDI descriptor
* @param void *inst The instance data connected_terminals
* @param uint32_t connected_terminals The number of terminals that are not
* internal nodes.
*
* @returns The number of nodes required after collapsing.
* */
static uint32_t collapse_nodes(const OsdiDescriptor *descr, void *inst,
uint32_t connected_terminals) {
/* access data inside instance */
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
bool *collapsed = (bool *)(((char *)inst) + descr->collapsed_offset);
/* without collapsing just return the total number of nodes */
uint32_t num_nodes = descr->num_nodes;
/* populate nodes with themselves*/
for (uint32_t i = 0; i < descr->num_nodes; i++) {
node_mapping[i] = i;
}
for (uint32_t i = 0; i < descr->num_collapsible; i++) {
/* check if the collapse hint (V(x,y) <+ 0) was executed */
if (!collapsed[i]) {
continue;
}
uint32_t from = descr->collapsible[i].node_1;
uint32_t to = descr->collapsible[i].node_2;
/* terminals created by the simulator cannot be collapsed
*/
if (node_mapping[from] < connected_terminals &&
(to == UINT32_MAX || node_mapping[to] < connected_terminals ||
node_mapping[to] == UINT32_MAX)) {
continue;
}
/* ensure that to is always the smaller node */
if (to != UINT32_MAX && node_mapping[from] < node_mapping[to]) {
uint32_t temp = from;
from = to;
to = temp;
}
from = node_mapping[from];
if (to != UINT32_MAX) {
to = node_mapping[to];
}
/* replace nodes mapped to from with to and reduce the number of nodes */
for (uint32_t j = 0; j < descr->num_nodes; j++) {
if (node_mapping[j] == from) {
node_mapping[j] = to;
} else if (node_mapping[j] > from && node_mapping[j] != UINT32_MAX) {
node_mapping[j] -= 1;
}
}
num_nodes -= 1;
}
return num_nodes;
}
/* replace node mapping local to the current instance (created by
* collapse_nodes) with global node indicies allocated with CKTmkVolt */
static void write_node_mapping(const OsdiDescriptor *descr, void *inst,
uint32_t *nodes) {
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (node_mapping[i] == UINT32_MAX) {
/* gnd node */
node_mapping[i] = 0;
} else {
node_mapping[i] = nodes[node_mapping[i]];
}
}
}
/* NGSPICE state vectors for an instance are always continous so we just write
* state_start .. state_start + num_state to state_idx */
static void write_state_ids(const OsdiDescriptor *descr, void *inst,
uint32_t state_start) {
uint32_t *state_idx = (uint32_t *)(((char *)inst) + descr->state_idx_off);
for (uint32_t i = 0; i < descr->num_states; i++) {
state_idx[i] = state_start + i;
}
}
static int init_matrix(SMPmatrix *matrix, const OsdiDescriptor *descr,
void *inst) {
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
double **jacobian_ptr_resist =
(double **)(((char *)inst) + descr->jacobian_ptr_resist_offset);
for (uint32_t i = 0; i < descr->num_jacobian_entries; i++) {
uint32_t equation = descr->jacobian_entries[i].nodes.node_1;
uint32_t unkown = descr->jacobian_entries[i].nodes.node_2;
equation = node_mapping[equation];
unkown = node_mapping[unkown];
double *ptr = SMPmakeElt(matrix, (int)equation, (int)unkown);
if (ptr == NULL) {
return (E_NOMEM);
}
jacobian_ptr_resist[i] = ptr;
uint32_t react_off = descr->jacobian_entries[i].react_ptr_off;
// complex number for ac analysis
if (react_off != UINT32_MAX) {
double **jacobian_ptr_react = (double **)(((char *)inst) + react_off);
*jacobian_ptr_react = ptr + 1;
}
}
return (OK);
}
int OSDIsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt,
int *states) {
OsdiInitInfo init_info;
OsdiNgspiceHandle handle;
GENmodel *gen_model;
int res;
int error;
CKTnode *tmp;
GENinstance *gen_inst;
int err;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_;
/* setup a temporary buffer */
uint32_t *node_ids = TMALLOC(uint32_t, descr->num_nodes);
/* determine the number of states required by each instance */
int num_states = (int)descr->num_states;
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (descr->nodes[i].react_residual_off != UINT32_MAX) {
num_states += 2;
}
}
for (gen_model = inModel; gen_model; gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
/* setup model parameter (setup_model)*/
handle = (OsdiNgspiceHandle){.kind = 1, .name = gen_model->GENmodName};
descr->setup_model((void *)&handle, model, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_model";
continue;
}
for (gen_inst = gen_model->GENinstances; gen_inst;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
/* special handling for temperature parameters */
double temp = ckt->CKTtemp;
OsdiExtraInstData *extra_inst_data =
osdi_extra_instance_data(entry, gen_inst);
if (extra_inst_data->temp_given) {
temp = extra_inst_data->temp;
}
if (extra_inst_data->dt_given) {
temp += extra_inst_data->dt;
}
/* find number of connected ports to allow evaluation of $port_connected
* and to handle node collapsing correctly later
* */
int *terminals = (int *)(gen_inst + 1);
uint32_t connected_terminals = descr->num_terminals;
for (uint32_t i = 0; i < descr->num_terminals; i++) {
if (terminals[i] == -1) {
connected_terminals = i;
break;
}
}
/* calculate op independent data, init instance parameters and determine
which collapsing occurs*/
handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname};
descr->setup_instance((void *)&handle, inst, model, temp,
connected_terminals, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_instance";
continue;
}
/* setup the instance nodes */
uint32_t num_nodes = collapse_nodes(descr, inst, connected_terminals);
/* copy terminals */
memcpy(node_ids, gen_inst + 1, sizeof(int) * connected_terminals);
/* create internal nodes as required */
for (uint32_t i = connected_terminals; i < num_nodes; i++) {
// TODO handle currents correctly
if (descr->nodes[i].is_flow) {
error = CKTmkCur(ckt, &tmp, gen_inst->GENname, descr->nodes[i].name);
} else {
error = CKTmkVolt(ckt, &tmp, gen_inst->GENname, descr->nodes[i].name);
}
if (error)
return (error);
node_ids[i] = (uint32_t)tmp->number;
// TODO nodeset?
}
write_node_mapping(descr, inst, node_ids);
/* now that we have the node mapping we can create the matrix entries */
err = init_matrix(matrix, descr, inst);
if (err) {
return err;
}
/* reserve space in the state vector*/
gen_inst->GENstate = *states;
write_state_ids(descr, inst, (uint32_t)*states);
*states += num_states;
}
}
free(node_ids);
return (OK);
}
/* OSDI does not differentiate between setup and temperature update so we just
* call the setup routines again and assume that node collapsing (and therefore
* node mapping) stays the same
*/
extern int OSDItemp(GENmodel *inModel, CKTcircuit *ckt) {
OsdiInitInfo init_info;
OsdiNgspiceHandle handle;
GENmodel *gen_model;
int res;
GENinstance *gen_inst;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
OsdiSimParas sim_params_ = get_simparams(ckt);
OsdiSimParas *sim_params = &sim_params_;
for (gen_model = inModel; gen_model != NULL;
gen_model = gen_model->GENnextModel) {
void *model = osdi_model_data(gen_model);
handle = (OsdiNgspiceHandle){.kind = 4, .name = gen_model->GENmodName};
descr->setup_model((void *)&handle, model, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_model (OSDItemp)";
continue;
}
for (gen_inst = gen_model->GENinstances; gen_inst != NULL;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// special handleing for temperature parameters
double temp = ckt->CKTtemp;
OsdiExtraInstData *extra_inst_data =
osdi_extra_instance_data(entry, gen_inst);
if (extra_inst_data->temp_given) {
temp = extra_inst_data->temp;
}
if (extra_inst_data->dt_given) {
temp += extra_inst_data->dt;
}
handle = (OsdiNgspiceHandle){.kind = 2, .name = gen_inst->GENname};
/* find number of connected ports to allow evaluation of $port_connected
* and to handle node collapsing correctly later
* */
int *terminals = (int *)(gen_inst + 1);
uint32_t connected_terminals = descr->num_terminals;
for (uint32_t i = 0; i < descr->num_terminals; i++) {
if (terminals[i] == -1) {
connected_terminals = i;
break;
}
}
descr->setup_instance((void *)&handle, inst, model, temp,
connected_terminals, sim_params, &init_info);
res = handle_init_info(init_info, descr);
if (res) {
errRtn = "OSDI setup_instance (OSDItemp)";
continue;
}
// TODO check that there are no changes in node collapse?
}
}
return (OK);
}
/* delete internal nodes
*/
extern int OSDIunsetup(GENmodel *inModel, CKTcircuit *ckt) {
GENmodel *gen_model;
GENinstance *gen_inst;
int num;
OsdiRegistryEntry *entry = osdi_reg_entry_model(inModel);
const OsdiDescriptor *descr = entry->descriptor;
for (gen_model = inModel; gen_model != NULL;
gen_model = gen_model->GENnextModel) {
for (gen_inst = gen_model->GENinstances; gen_inst != NULL;
gen_inst = gen_inst->GENnextInstance) {
void *inst = osdi_instance_data(entry, gen_inst);
// reset is collapsible
bool *collapsed = (bool *)(((char *)inst) + descr->collapsed_offset);
memset(collapsed, 0, sizeof(bool) * descr->num_collapsible);
uint32_t *node_mapping =
(uint32_t *)(((char *)inst) + descr->node_mapping_offset);
for (uint32_t i = 0; i < descr->num_nodes; i++) {
num = (int)node_mapping[i];
// hand coded implementations just know which nodes were collapsed
// however nodes may be collapsed multiple times so we can't easily use
// an approach like that instead we delete all nodes
// Deleting twiche with CKLdltNNum is fine (entry is already removed
// from the linked list and therefore no action is taken).
// However CKTdltNNum (rightfully) throws an error when trying to delete
// an external node. Therefore we need to check for each node that it is
// an internal node
if (ckt->prev_CKTlastNode->number &&
num > ckt->prev_CKTlastNode->number) {
CKTdltNNum(ckt, num);
}
}
}
}
return (OK);
}

43
src/osdi/osditrunc.c Normal file
View File

@ -0,0 +1,43 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*/
#include "ngspice/cktdefs.h"
#include "osdidefs.h"
int OSDItrunc(GENmodel *in_model, CKTcircuit *ckt, double *timestep) {
OsdiRegistryEntry *entry = osdi_reg_entry_model(in_model);
const OsdiDescriptor *descr = entry->descriptor;
uint32_t offset = descr->bound_step_offset;
bool has_boundstep = offset != UINT32_MAX;
offset += entry->inst_offset;
for (GENmodel *model = in_model; model; model = model->GENnextModel) {
for (GENinstance *inst = model->GENinstances; inst;
inst = inst->GENnextInstance) {
if (has_boundstep) {
double *del = (double *)(((char *)inst) + offset);
if (*del < *timestep) {
*timestep = *del;
}
}
int state = inst->GENstate;
for (uint32_t i = 0; i < descr->num_nodes; i++) {
if (descr->nodes[i].react_residual_off != UINT32_MAX) {
CKTterr(state, ckt, timestep);
state += 2;
}
}
}
}
return 0;
}

View File

@ -356,11 +356,7 @@ void load_alldevs(void){
}
#endif
/*-------------------- XSPICE additions below ----------------------*/
#ifdef XSPICE
#include "ngspice/mif.h"
#include "ngspice/cm.h"
#include "ngspice/cpextern.h"
#if defined(XSPICE) || defined(OSDI)
#include "ngspice/fteext.h" /* for ft_sim */
#include "ngspice/cktdefs.h" /* for DEVmaxnum */
@ -379,6 +375,13 @@ static void relink(void) {
return;
}
#endif
/*-------------------- XSPICE additions below ----------------------*/
#ifdef XSPICE
#include "ngspice/cm.h"
#include "ngspice/cpextern.h"
#include "ngspice/mif.h"
int add_device(int n, SPICEdev **devs, int flag) {
int i;
int dnum = DEVNUM + n;
@ -566,3 +569,35 @@ static void free_dlerr_msg(char *msg)
#endif /* Windows emulation of dlopen, dlsym, and dlerr */
#endif
/*-------------------- end of XSPICE additions ----------------------*/
#ifdef OSDI
#include "ngspice/osdiitf.h"
static int osdi_add_device(int n, OsdiRegistryEntry *devs) {
int i;
int dnum = DEVNUM + n;
DEVices = TREALLOC(SPICEdev *, DEVices, dnum);
#ifdef XSPICE
DEVicesfl = TREALLOC(int, DEVicesfl, dnum);
#endif
for (i = 0; i < n; i++) {
#ifdef TRACE
printf("Added device: %s\n", devs[i]->DEVpublic.name);
#endif
DEVices[DEVNUM + i] = osdi_create_spicedev(&devs[i]);
}
DEVNUM += n;
relink();
return 0;
}
int load_osdi(const char *path) {
OsdiObjectFile file = load_object_file(path);
if (file.num_entries < 0) {
return file.num_entries;
}
osdi_add_device(file.num_entries, file.entrys);
return 0;
}
#endif

View File

@ -15,5 +15,9 @@ int DEVflag(int type);
void load_alldevs(void);
int load_dev(char *name);
#endif
#ifdef OSDI
int load_osdi(const char *);
#endif
#endif

View File

@ -71,6 +71,11 @@ libinp_la_SOURCES = \
sperror.c \
inpxx.h
if OSDI_WANTED
libinp_la_SOURCES += inp2a.c
endif
AM_CPPFLAGS = @AM_CPPFLAGS@ -I$(top_srcdir)/src/include -I$(top_srcdir)/src/frontend
AM_CFLAGS = $(STATIC)
AM_YFLAGS = -d

109
src/spicelib/parser/inp2a.c Normal file
View File

@ -0,0 +1,109 @@
/**********
Copyright 1990 Regents of the University of California. All rights reserved.
Author: 1988 Thomas L. Quarles
Modified: 2001 Paolo Nenzi (Cider Integration)
**********/
#include "ngspice/ngspice.h"
#include "ngspice/devdefs.h"
#include "ngspice/fteext.h"
#include "ngspice/ifsim.h"
#include "ngspice/inpdefs.h"
#include "ngspice/inpmacs.h"
#include "inpxx.h"
#include <stdio.h>
#ifdef XSPICE
#include "ngspice/mifproto.h"
#endif
void INP2A(CKTcircuit *ckt, INPtables *tab, struct card *current) {
/* Mname <node> <node> <node> <node> <model> [L=<val>]
* [W=<val>] [AD=<val>] [AS=<val>] [PD=<val>]
* [PS=<val>] [NRD=<val>] [NRS=<val>] [OFF]
* [IC=<val>,<val>,<val>]
*/
int type; /* the type the model says it is */
char *line; /* the part of the current line left to parse */
char *name; /* the resistor's name */
// limit to at most 20 nodes
const int max_i = 20;
CKTnode *node[20];
int error; /* error code temporary */
int numnodes; /* flag indicating 4 or 5 (or 6 or 7) nodes */
GENinstance *fast; /* pointer to the actual instance */
int waslead; /* flag to indicate that funny unlabeled number was found */
double leadval; /* actual value of unlabeled number */
INPmodel *thismodel; /* pointer to model description for user's model */
GENmodel *mdfast; /* pointer to the actual model */
int i;
line = current->line;
INPgetNetTok(&line, &name, 1);
INPinsert(&name, tab);
for (i = 0;; i++) {
char *token;
INPgetNetTok(&line, &token, 1);
if (i >= 2) {
txfree(INPgetMod(ckt, token, &thismodel, tab));
/* /1* check if using model binning -- pass in line since need 'l' and 'w' *1/ */
/* if (!thismodel) */
/* txfree(INPgetModBin(ckt, token, &thismodel, tab, line)); */
if (thismodel) {
INPinsert(&token, tab);
break;
}
}
if (i >= max_i) {
LITERR("could not find a valid modelname");
return;
}
INPtermInsert(ckt, &token, tab, &node[i]);
}
type = thismodel->INPmodType;
mdfast = thismodel->INPmodfast;
IFdevice *dev = ft_sim->devices[type];
if (!dev->registry_entry) {
#ifdef XSPICE
MIF_INP2A(ckt, tab, current);
#else
LITERR("incorrect model type! Expected OSDI device");
#endif
return;
}
if (i == 0) {
LITERR("not enough nodes");
return;
}
if (i > *dev->terms) {
LITERR("too many nodes connected to instance");
return;
}
numnodes = i;
IFC(newInstance, (ckt, mdfast, &fast, name));
for (i = 0; i < *dev->terms; i++)
if (i < numnodes)
IFC(bindNode, (ckt, fast, i + 1, node[i]));
else
GENnode(fast)[i] = -1;
PARSECALL((&line, ckt, type, fast, &leadval, &waslead, tab));
if (waslead)
LITERR(" error: no unlabeled parameter permitted on osdi devices\n");
}

View File

@ -679,7 +679,7 @@ char *INPdomodel(CKTcircuit *ckt, struct card *image, INPtables * tab)
/* -------- Default action --------- */
else {
#ifndef XSPICE
#if !defined(XSPICE) && !defined(OSDI)
type = -1;
err = tprintf("unknown model type %s - ignored\n", type_name);
#else

View File

@ -13,8 +13,11 @@ Modified: 2001 Paolo Nenzi (Cider Integration)
#include "ngspice/cpstd.h"
#include "ngspice/fteext.h"
#include "ngspice/compatmode.h"
#include "ngspice/devdefs.h"
#include "inpxx.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#ifdef CIDER
@ -111,6 +114,14 @@ create_model(CKTcircuit *ckt, INPmodel *modtmp, INPtables *tab)
INPgetNetTok(&line, &parm, 1); /* throw away 'modname' */
tfree(parm);
#ifdef OSDI
/* osdi models don't accept their device type as an argument */
if (device->registry_entry){
INPgetNetTok(&line, &parm, 1); /* throw away osdi */
tfree(parm);
}
#endif
while (*line) {
INPgetTok(&line, &parm, 1);
if (!*parm) {
@ -130,6 +141,7 @@ create_model(CKTcircuit *ckt, INPmodel *modtmp, INPtables *tab)
/* just grab the number and throw away */
/* since we already have that info from pass1 */
INPgetValue(ckt, &line, IF_REAL, tab);
} else {
p = find_instance_parameter(parm, device);

View File

@ -99,7 +99,12 @@ void INPpas2(CKTcircuit *ckt, struct card *data, INPtables * tab, TSKtask *task)
/* blank line (tab leading) */
break;
#ifdef XSPICE
#ifdef OSDI
case 'A': /* Aname <cm connections> <mname> */
// OSDI handles xspice
INP2A(ckt, tab, current);
break;
#elif XSPICE
/* gtri - add - wbk - 10/23/90 - add case for 'A' devices */
case 'A': /* Aname <cm connections> <mname> */
@ -245,7 +250,6 @@ void INPpas2(CKTcircuit *ckt, struct card *data, INPtables * tab, TSKtask *task)
case 'B':
/* Bname <node> <node> [V=expr] [I=expr] */
/* Arbitrary source. */
INP2B(ckt, tab, current);
break;
case '.': /* .<something> Many possibilities */

View File

@ -8,6 +8,9 @@
/* inp2xx.c */
#ifdef OSDI
void INP2A(CKTcircuit *ckt, INPtables *tab, struct card *current);
#endif
void INP2B(CKTcircuit *ckt, INPtables *tab, struct card *current);
void INP2C(CKTcircuit *ckt, INPtables *tab, struct card *current);
void INP2D(CKTcircuit *ckt, INPtables *tab, struct card *current);

View File

View File

@ -0,0 +1,374 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 3
#define P 0
#define M 1
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 4
#define P_P 0
#define P_M 1
#define M_P 2
#define M_M 3
// The model structure for the diode
typedef struct CapacitorModel
{
double C;
bool C_given;
} CapacitorModel;
// The instace structure for the diode
typedef struct CapacitorInstance
{
double temperature;
double rhs_resist[NUM_NODES];
double rhs_react[NUM_NODES];
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
} CapacitorInstance;
// implementation of the access function as defined by the OSDI spec
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
{
CapacitorModel *model = (CapacitorModel *)model_;
CapacitorInstance *inst = (CapacitorInstance *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
value = (void *)&model->C;
given = &model->C_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET)
{
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
OsdiInitInfo setup_model(void *_handle, void *model_)
{
CapacitorModel *model = (CapacitorModel *)model_;
// set parameters and check bounds
if (!model->C_given)
{
model->C = 1e-15;
}
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_,
double temperature, uint32_t _num_terminals)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
CapacitorModel *model = (CapacitorModel *)model_;
inst->temperature = temperature;
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the eval function as defined in the OSDI spec
uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags,
double *prev_solve, OsdiSimParas *sim_params)
{
CapacitorModel *model = (CapacitorModel *)model_;
CapacitorInstance *inst = (CapacitorInstance *)inst_;
// get voltages
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
double vpm = vp - vm;
double gmin = 1e-12;
for (int i = 0; sim_params->names[i] != NULL; i++)
{
if (strcmp(sim_params->names[i], "gmin") == 0)
{
gmin = sim_params->vals[i];
}
}
double qc_vpm = model->C;
double qc = model->C * vpm;
////////////////////////////////
// evaluate model equations
////////////////////////////////
if (flags & CALC_REACT_RESIDUAL)
{
// write react rhs
inst->rhs_react[P] = qc;
inst->rhs_react[M] = -qc;
}
//////////////////
// write Jacobian
//////////////////
if (flags & CALC_REACT_JACOBIAN)
{
// write react matrix
// stamp Qd between nodes A and Ci depending also on dT
inst->jacobian_react[P_P] = qc_vpm;
inst->jacobian_react[P_M] = -qc_vpm;
inst->jacobian_react[M_P] = -qc_vpm;
inst->jacobian_react[M_M] = qc_vpm;
}
return 0;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens)
{
// TODO add noise to example
}
#define LOAD_RHS_RESIST(name) \
dst[inst->node_off[name]] += inst->rhs_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
void load_residual_resist(void *inst_, double *dst)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_RHS_RESIST(P)
LOAD_RHS_RESIST(M)
}
#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name];
// implementation of the load_rhs_react function as defined in the OSDI spec
void load_residual_react(void *inst_, double *dst)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_RHS_REACT(P)
LOAD_RHS_REACT(M)
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
void load_jacobian_resist(void *inst_)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_MATRIX_RESIST(P_P)
LOAD_MATRIX_RESIST(P_M)
LOAD_MATRIX_RESIST(M_P)
LOAD_MATRIX_RESIST(M_M)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
void load_jacobian_react(void *inst_, double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_MATRIX_REACT(P_P)
LOAD_MATRIX_REACT(M_M)
LOAD_MATRIX_REACT(P_M)
LOAD_MATRIX_REACT(M_P)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
void load_jacobian_tran(void *inst_, double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
// set dc stamps
load_jacobian_resist(inst_);
// add reactive contributions
LOAD_MATRIX_TRAN(P_P)
LOAD_MATRIX_TRAN(M_M)
LOAD_MATRIX_TRAN(M_P)
LOAD_MATRIX_TRAN(M_M)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
dst[inst->node_off[P]] += inst->jacobian_resist[P_M] * vm +
inst->jacobian_resist[P_P] * vp -
inst->rhs_resist[P];
dst[inst->node_off[M]] += inst->jacobian_resist[M_P] * vp +
inst->jacobian_resist[M_M] * vm -
inst->rhs_resist[M];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve,
double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
// set DC rhs
load_spice_rhs_dc(inst_, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[P]] +=
alpha * (inst->jacobian_react[P_P] * vp +
inst->jacobian_react[P_M] * vm);
dst[inst->node_off[M]] += alpha * (inst->jacobian_react[M_M] * vm +
inst->jacobian_react[M_P] * vp);
}
// structure that provides information of all nodes of the model
OsdiNode nodes[NUM_NODES] = {
{.name = "P", .units = "V", .is_reactive = true},
{.name = "M", .units = "V", .is_reactive = true},
};
// boolean array that tells which Jacobian entries are constant. Nothing is
// constant with selfheating, though.
bool const_jacobian_entries[NUM_MATRIX] = {};
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiNodePair jacobian_entries[NUM_MATRIX] = {
{P, P},
{P, M},
{M, P},
{M, M},
};
#define NUM_PARAMS 1
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"C"},
.num_alias = 0,
.description = "Capacitance",
.units = "Farad",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "capacitor_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 2,
.nodes = (OsdiNode *)&nodes,
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiNodePair *)&jacobian_entries,
.const_jacobian_entries = (bool *)&const_jacobian_entries,
// memory
.instance_size = sizeof(CapacitorInstance),
.model_size = sizeof(CapacitorModel),
.residual_resist_offset = offsetof(CapacitorInstance, rhs_resist),
.residual_react_offset = offsetof(CapacitorInstance, rhs_react),
.node_mapping_offset = offsetof(CapacitorInstance, node_off),
.jacobian_resist_offset = offsetof(CapacitorInstance, jacobian_resist),
.jacobian_react_offset = offsetof(CapacitorInstance, jacobian_react),
.jacobian_ptr_resist_offset = offsetof(CapacitorInstance, jacobian_ptr_resist),
.jacobian_ptr_react_offset = offsetof(CapacitorInstance, jacobian_ptr_react),
// TODO add node collapsing to example
// node collapsing
.num_collapsible = 0,
.collapsible = NULL,
.is_collapsible_offset = 0,
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 0,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// setup
.access = &osdi_access,
.setup_model = &setup_model,
.setup_instance = &setup_instance,
.eval = &eval,
.load_noise = &load_noise,
.load_residual_resist = &load_residual_resist,
.load_residual_react = &load_residual_react,
.load_spice_rhs_dc = &load_spice_rhs_dc,
.load_spice_rhs_tran = &load_spice_rhs_tran,
.load_jacobian_resist = &load_jacobian_resist,
.load_jacobian_react = &load_jacobian_react,
.load_jacobian_tran = &load_jacobian_tran,
}};

View File

@ -0,0 +1,43 @@
OSDI Capacitor Test
.options abstol=1e-15
* one voltage source for sweeping, one for sensing:
VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M)
Vsense Dx D DC 0
* model definitions:
.model cmod_osdi capacitor_va c=5e-12
*OSDI Capacitor:
*OSDI_ACTIVATE*A1 D 0 cmod_osdi
*Built-in Capacitor:
*BUILT_IN_ACTIVATE*C1 D 0 5e-12
.control
pre_osdi capacitor.osdi
set filetype=ascii
set wr_vecnames
set wr_singlescale
* a DC sweep from 0.3V to 1V
dc Vd 0.3 1.0 0.01
wrdata dc_sim.ngspice v(d) i(vsense)
* an AC sweep at Vd=0.5V
alter VD=0.5
ac dec 10 .01 10
wrdata ac_sim.ngspice v(d) i(vsense)
* a transient analysis
tran 100ms 500000ms
wrdata tr_sim.ngspice v(d) i(vsense)
* print number of iterations
rusage totiter
.endc
.end

View File

@ -0,0 +1,160 @@
""" test OSDI simulation of capacitor
"""
import os, shutil
import numpy as np
import pandas as pd
import sys
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from testing import prepare_test
# This test runs a DC, AC and Transient Simulation of a simple capacitor.
# The capacitor is available as a C file and needs to be compiled to a shared object
# and then bet put into /usr/local/share/ngspice/osdi:
#
# > make osdi_capacitor
# > cp capacitor_osdi.so /usr/local/share/ngspice/osdi/capacitor_osdi.so
#
# The integration test proves the functioning of the OSDI interface.
# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation.
directory = os.path.dirname(__file__)
def test_ngspice():
dir_osdi, dir_built_in = prepare_test(directory)
# read DC simulation results
dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
dc_data_built_in = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
# dc_data_built_in = pd.read_csv(
# os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\\s+"
# )
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_osdi["i(vsense)"].to_numpy()
# id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# read AC simulation results
ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
ac_data_built_in = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
# ac_data_built_in = pd.read_csv(
# os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\\s+"
# )
# read TR simulation results
tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
tr_data_built_in = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
# tr_data_built_in = pd.read_csv(
# os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\\s+"
# )
# test simulation results
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
np.testing.assert_allclose(id_osdi[0:20], id_built_in[0:20], rtol=0.01)
return (
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
)
if __name__ == "__main__":
(
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
) = test_ngspice()
import matplotlib.pyplot as plt
# DC Plot
pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"]
pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"]
fig, ax1 = plt.subplots()
ax1.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
ax1.plot(
dc_data_osdi["v(d)"],
dc_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
plt.legend()
# AC Plot
omega = 2 * np.pi * ac_data_osdi["frequency"]
z_analytical = 5e-12 * omega
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI"
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Re \\left\{ Y_{11} \\right\} (\\mathrm{mS})$")
plt.legend()
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense).1"] * 1e12 / omega,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"],
ac_data_osdi["i(vsense).1"] * 1e12 / omega,
label="OSDI",
)
plt.semilogx(
ac_data_osdi["frequency"],
np.ones_like(ac_data_osdi["frequency"]) * z_analytical * 1e12 / omega,
label="analytical",
linestyle="--",
marker="s",
)
plt.ylim(1, 9)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{pF})$")
plt.legend()
# TR plot
fig = plt.figure()
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.plot(
tr_data_osdi["time"] * 1e9,
tr_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
plt.xlabel(r"$t(\mathrm{nS})$")
plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$")
plt.legend()
plt.show()

View File

View File

913
test_cases/diode/diode.c Normal file
View File

@ -0,0 +1,913 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
extern OsdiLimFunction OSDI_LIM_TABLE[1];
extern uint32_t OSDI_LIM_TABLE_LEN;
#define sqrt2 1.4142135623730950488016887242097
#define IGNORE(x) (void)x
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 4
#define A 0
#define C 1
#define TNODE 2
#define CI 3
#define NUM_COLLAPSIBLE 2
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 14
#define CI_CI 0
#define CI_C 1
#define C_CI 2
#define C_C 3
#define A_A 4
#define A_CI 5
#define CI_A 6
#define A_TNODE 7
#define C_TNODE 8
#define CI_TNODE 9
#define TNODE_TNODE 10
#define TNODE_A 11
#define TNODE_C 12
#define TNODE_CI 13
// The model structure for the diode
typedef struct DiodeModel {
double Rs;
bool Rs_given;
double Is;
bool Is_given;
double zetars;
bool zetars_given;
double N;
bool N_given;
double Cj0;
bool Cj0_given;
double Vj;
bool Vj_given;
double M;
bool M_given;
double Rth;
bool Rth_given;
double zetarth;
bool zetarth_given;
double zetais;
bool zetais_given;
double Tnom;
bool Tnom_given;
double mfactor; // multiplication factor for parallel devices
bool mfactor_given;
// InitError errors[MAX_ERROR_NUM],
} DiodeModel;
// The instace structure for the diode
typedef struct DiodeInstace {
double mfactor; // multiplication factor for parallel devices
bool mfactor_given;
double temperature;
double residual_resist[NUM_NODES];
double lim_rhs_resist_A;
double lim_rhs_resist_CI;
double lim_rhs_react_A;
double lim_rhs_react_CI;
double residual_react_A;
double residual_react_CI;
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
bool collapsed[NUM_COLLAPSIBLE];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
uint32_t state_idx;
} DiodeInstace;
#define EXP_LIM 80.0
static double limexp(double x) {
if (x < EXP_LIM) {
return exp(x);
} else {
return exp(EXP_LIM) * (x + 1 - EXP_LIM);
}
}
static double dlimexp(double x) {
if (x < EXP_LIM) {
return exp(x);
} else {
return exp(EXP_LIM);
}
}
// implementation of the access function as defined by the OSDI spec
static void *osdi_access(void *inst_, void *model_, uint32_t id,
uint32_t flags) {
DiodeModel *model = (DiodeModel *)model_;
DiodeInstace *inst = (DiodeInstace *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
if (flags & ACCESS_FLAG_INSTANCE) {
value = (void *)&inst->mfactor;
given = &inst->mfactor_given;
} else {
value = (void *)&model->mfactor;
given = &model->mfactor_given;
}
break;
case 1:
value = (void *)&model->Rs;
given = &model->Rs_given;
break;
case 2:
value = (void *)&model->Is;
given = &model->Is_given;
break;
case 3:
value = (void *)&model->zetars;
given = &model->zetars_given;
break;
case 4:
value = (void *)&model->N;
given = &model->N_given;
break;
case 5:
value = (void *)&model->Cj0;
given = &model->Cj0_given;
break;
case 6:
value = (void *)&model->Vj;
given = &model->Vj_given;
break;
case 7:
value = (void *)&model->M;
given = &model->M_given;
break;
case 8:
value = &model->Rth;
given = &model->Rth_given;
break;
case 9:
value = (void *)&model->zetarth;
given = &model->zetarth_given;
break;
case 10:
value = (void *)&model->zetais;
given = &model->zetais_given;
break;
case 11:
value = (void *)&model->Tnom;
given = &model->Tnom_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET) {
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
static void setup_model(void *handle, void *model_, OsdiSimParas *sim_params,
OsdiInitInfo *res) {
DiodeModel *model = (DiodeModel *)model_;
IGNORE(handle);
IGNORE(sim_params);
// set parameters and check bounds
if (!model->mfactor_given) {
model->mfactor = 1.0;
}
if (!model->Rs_given) {
model->Rs = 1e-9;
}
if (!model->Is_given) {
model->Is = 1e-14;
}
if (!model->zetars_given) {
model->zetars = 0;
}
if (!model->N_given) {
model->N = 1;
}
if (!model->Cj0_given) {
model->Cj0 = 0;
}
if (!model->Vj_given) {
model->Vj = 1.0;
}
if (!model->M_given) {
model->M = 0.5;
}
if (!model->Rth_given) {
model->Rth = 0;
}
if (!model->zetarth_given) {
model->zetarth = 0;
}
if (!model->zetais_given) {
model->zetais = 0;
}
if (!model->Tnom_given) {
model->Tnom = 300;
}
*res = (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
static void setup_instance(void *handle, void *inst_, void *model_,
double temperature, uint32_t num_terminals,
OsdiSimParas *sim_params, OsdiInitInfo *res) {
IGNORE(handle);
IGNORE(num_terminals);
IGNORE(sim_params);
DiodeInstace *inst = (DiodeInstace *)inst_;
DiodeModel *model = (DiodeModel *)model_;
// Here the logic for node collapsing ist implemented. The indices in this
// list must adhere to the "collapsible" List of node pairs.
if (model->Rs < 1e-9) { // Rs between Ci C
inst->collapsed[0] = true;
}
if (model->Rth < 1e-9) { // Rs between Ci C
inst->collapsed[1] = true;
}
if (!inst->mfactor_given) {
if (model->mfactor_given) {
inst->mfactor = model->mfactor;
} else {
inst->mfactor = 1;
}
}
inst->temperature = temperature;
*res = (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
#define CONSTsqrt2 1.4142135623730950488016887242097
typedef double (*pnjlim_t)(bool, bool *, double, double, double, double);
// implementation of the eval function as defined in the OSDI spec
static uint32_t eval(void *handle, void *inst_, void *model_,
OsdiSimInfo *info) {
IGNORE(handle);
DiodeModel *model = (DiodeModel *)model_;
DiodeInstace *inst = (DiodeInstace *)inst_;
// get voltages
double *prev_solve = info->prev_solve;
double va = prev_solve[inst->node_off[A]];
double vc = prev_solve[inst->node_off[C]];
double vci = prev_solve[inst->node_off[CI]];
double vdtj = prev_solve[inst->node_off[TNODE]];
double vcic = vci - vc;
double vaci = va - vci;
double gmin = 1e-12;
for (int i = 0; info->paras.names[i] != NULL; i++) {
if (strcmp(info->paras.names[i], "gmin") == 0) {
gmin = info->paras.vals[i];
}
}
uint32_t ret_flags = 0;
////////////////////////////////
// evaluate model equations
////////////////////////////////
// temperature update
double pk = 1.3806503e-23;
double pq = 1.602176462e-19;
double t_dev = inst->temperature + vdtj;
double tdev_tnom = t_dev / model->Tnom;
double rs_t = model->Rs * pow(tdev_tnom, model->zetars);
double rth_t = model->Rth * pow(tdev_tnom, model->zetarth);
double is_t = model->Is * pow(tdev_tnom, model->zetais);
double vt = t_dev * pk / pq;
double delvaci = 0.0;
if (info->flags & ENABLE_LIM && OSDI_LIM_TABLE[0].func_ptr) {
double vte = inst->temperature * pk / pq;
bool icheck = false;
double vaci_old = info->prev_state[inst->state_idx];
pnjlim_t pnjlim = OSDI_LIM_TABLE[0].func_ptr;
double vaci_new = pnjlim(info->flags & INIT_LIM, &icheck, vaci, vaci_old,
vte, vte * log(vte / (sqrt2 * model->Is)));
printf("%g %g\n", vaci, vaci_new);
delvaci = vaci_new - vaci;
vaci = vaci_new;
info->prev_state[inst->state_idx] = vaci;
} else {
printf("ok?");
}
// derivatives w.r.t. temperature
double rs_dt = model->zetars * model->Rs *
pow(tdev_tnom, model->zetars - 1.0) / model->Tnom;
double rth_dt = model->zetarth * model->Rth *
pow(tdev_tnom, model->zetarth - 1.0) / model->Tnom;
double is_dt = model->zetais * model->Is *
pow(tdev_tnom, model->zetais - 1.0) / model->Tnom;
double vt_tj = pk / pq;
// evaluate model equations and calculate all derivatives
// diode current
double id = is_t * (limexp(vaci / (model->N * vt)) - 1.0);
double gd = is_t / vt * dlimexp(vaci / (model->N * vt));
double gdt = -is_t * dlimexp(vaci / (model->N * vt)) * vaci / model->N / vt /
vt * vt_tj +
1.0 * exp((vaci / (model->N * vt)) - 1.0) * is_dt;
// resistor
double irs = 0;
double g = 0;
double grt = 0;
if (!inst->collapsed[0]) {
irs = vcic / rs_t;
g = 1.0 / rs_t;
grt = -irs / rs_t * rs_dt;
}
// thermal resistance
double irth = 0;
double gt = 0;
if (!inst->collapsed[1]) {
irth = vdtj / rth_t;
gt = 1.0 / rth_t - irth / rth_t * rth_dt;
}
// charge
double vf = model->Vj * (1.0 - pow(3.04, -1.0 / model->M));
double x = (vf - vaci) / vt;
double x_vt = -x / vt;
double x_dtj = x_vt * vt_tj;
double x_vaci = -1.0 / vt;
double y = sqrt(x * x + 1.92);
double y_x = 0.5 / y * 2.0 * x;
double y_vaci = y_x * x_vaci;
double y_dtj = y_x * x_dtj;
double vd = vf - vt * (x + y) / (2.0);
double vd_x = -vt / 2.0;
double vd_y = -vt / 2.0;
double vd_vt = -(x + y) / (2.0);
double vd_dtj = vd_x * x_dtj + vd_y * y_dtj + vd_vt * vt_tj;
double vd_vaci = vd_x * x_vaci + vd_y * y_vaci;
double qd = model->Cj0 * vaci * model->Vj *
(1.0 - pow(1.0 - vd / model->Vj, 1.0 - model->M)) /
(1.0 - model->M);
double qd_vd = model->Cj0 * model->Vj / (1.0 - model->M) * (1.0 - model->M) *
pow(1.0 - vd / model->Vj, 1.0 - model->M - 1.0) / model->Vj;
double qd_dtj = qd_vd * vd_dtj;
double qd_vaci = qd_vd * vd_vaci;
// thermal power source = current source
double ith = id * vaci;
double ith_vtj = gdt * vaci;
double ith_vcic = 0;
double ith_vaci = gd * vaci + id;
if (!inst->collapsed[0]) {
ith_vcic = 2.0 * vcic / rs_t;
ith += pow(vcic, 2.0) / rs_t;
ith_vtj -= -pow(vcic, 2.0) / rs_t / rs_t * rs_dt;
}
id += gmin * vaci;
gd += gmin;
double mfactor = inst->mfactor;
////////////////
// write rhs
////////////////
if (info->flags & CALC_RESIST_RESIDUAL) {
// write resist rhs
inst->residual_resist[A] = id * mfactor;
inst->residual_resist[CI] = -id * mfactor + irs * mfactor;
inst->residual_resist[C] = -irs * mfactor;
inst->residual_resist[TNODE] = -ith * mfactor + irth * mfactor;
}
if (info->flags & CALC_RESIST_LIM_RHS) {
// write resist rhs
inst->lim_rhs_resist_A = gd * mfactor * delvaci;
inst->lim_rhs_resist_CI = -gd * mfactor * delvaci;
}
if (info->flags & CALC_REACT_RESIDUAL) {
// write react rhs
inst->residual_react_A = qd * mfactor;
inst->residual_react_CI = -qd * mfactor;
}
if (info->flags & CALC_REACT_LIM_RHS) {
// write resist rhs
inst->lim_rhs_react_A = qd_vaci * mfactor * delvaci;
inst->lim_rhs_react_CI = -qd_vaci * mfactor * delvaci;
}
//////////////////
// write Jacobian
//////////////////
if (info->flags & CALC_RESIST_JACOBIAN) {
// stamp diode (current flowing from Ci into A)
inst->jacobian_resist[A_A] = gd * mfactor;
inst->jacobian_resist[A_CI] = -gd * mfactor;
inst->jacobian_resist[CI_A] = -gd * mfactor;
inst->jacobian_resist[CI_CI] = gd * mfactor;
// diode thermal
inst->jacobian_resist[A_TNODE] = gdt * mfactor;
inst->jacobian_resist[CI_TNODE] = -gdt * mfactor;
// stamp resistor (current flowing from C into CI)
inst->jacobian_resist[CI_CI] += g * mfactor;
inst->jacobian_resist[CI_C] = -g * mfactor;
inst->jacobian_resist[C_CI] = -g * mfactor;
inst->jacobian_resist[C_C] = g * mfactor;
// resistor thermal
inst->jacobian_resist[CI_TNODE] = grt * mfactor;
inst->jacobian_resist[C_TNODE] = -grt * mfactor;
// stamp rth flowing into node dTj
inst->jacobian_resist[TNODE_TNODE] = gt * mfactor;
// stamp ith flowing out of T node
inst->jacobian_resist[TNODE_TNODE] -= ith_vtj * mfactor;
inst->jacobian_resist[TNODE_CI] = (ith_vcic - ith_vaci) * mfactor;
inst->jacobian_resist[TNODE_C] = -ith_vcic * mfactor;
inst->jacobian_resist[TNODE_A] = ith_vaci * mfactor;
}
if (info->flags & CALC_REACT_JACOBIAN) {
// write react matrix
// stamp Qd between nodes A and Ci depending also on dT
inst->jacobian_react[A_A] = qd_vaci * mfactor;
inst->jacobian_react[A_CI] = -qd_vaci * mfactor;
inst->jacobian_react[CI_A] = -qd_vaci * mfactor;
inst->jacobian_react[CI_CI] = qd_vaci * mfactor;
inst->jacobian_react[A_TNODE] = qd_dtj * mfactor;
inst->jacobian_react[CI_TNODE] = -qd_dtj * mfactor;
}
return ret_flags;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
static void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens) {
IGNORE(inst);
IGNORE(model);
IGNORE(freq);
IGNORE(noise_dens);
IGNORE(ln_noise_dens);
// TODO add noise to example
}
#define LOAD_RESIDUAL_RESIST(name) \
dst[inst->node_off[name]] += inst->residual_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
static void load_residual_resist(void *inst_, void *model, double *dst) {
DiodeInstace *inst = (DiodeInstace *)inst_;
IGNORE(model);
LOAD_RESIDUAL_RESIST(A)
LOAD_RESIDUAL_RESIST(CI)
LOAD_RESIDUAL_RESIST(C)
LOAD_RESIDUAL_RESIST(TNODE)
}
// implementation of the load_rhs_react function as defined in the OSDI spec
static void load_residual_react(void *inst_, void *model, double *dst) {
IGNORE(model);
DiodeInstace *inst = (DiodeInstace *)inst_;
dst[inst->node_off[A]] += inst->residual_react_A;
dst[inst->node_off[CI]] += inst->residual_react_CI;
}
// implementation of the load_lim_rhs_resist function as defined in the OSDI
// spec
static void load_lim_rhs_resist(void *inst_, void *model, double *dst) {
DiodeInstace *inst = (DiodeInstace *)inst_;
IGNORE(model);
dst[inst->node_off[A]] += inst->lim_rhs_resist_A;
dst[inst->node_off[CI]] += inst->lim_rhs_resist_CI;
}
// implementation of the load_lim_rhs_react function as defined in the OSDI spec
static void load_lim_rhs_react(void *inst_, void *model, double *dst) {
DiodeInstace *inst = (DiodeInstace *)inst_;
IGNORE(model);
dst[inst->node_off[A]] += inst->lim_rhs_react_A;
dst[inst->node_off[CI]] += inst->lim_rhs_react_CI;
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
static void load_jacobian_resist(void *inst_, void *model) {
IGNORE(model);
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_MATRIX_RESIST(A_A)
LOAD_MATRIX_RESIST(A_CI)
LOAD_MATRIX_RESIST(A_TNODE)
LOAD_MATRIX_RESIST(CI_A)
LOAD_MATRIX_RESIST(CI_CI)
LOAD_MATRIX_RESIST(CI_C)
LOAD_MATRIX_RESIST(CI_TNODE)
LOAD_MATRIX_RESIST(C_CI)
LOAD_MATRIX_RESIST(C_C)
LOAD_MATRIX_RESIST(C_TNODE)
LOAD_MATRIX_RESIST(TNODE_TNODE)
LOAD_MATRIX_RESIST(TNODE_A)
LOAD_MATRIX_RESIST(TNODE_C)
LOAD_MATRIX_RESIST(TNODE_CI)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
static void load_jacobian_react(void *inst_, void *model, double alpha) {
IGNORE(model);
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_MATRIX_REACT(A_A)
LOAD_MATRIX_REACT(A_CI)
LOAD_MATRIX_REACT(CI_A)
LOAD_MATRIX_REACT(CI_CI)
LOAD_MATRIX_REACT(A_TNODE)
LOAD_MATRIX_REACT(CI_TNODE)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
static void load_jacobian_tran(void *inst_, void *model, double alpha) {
DiodeInstace *inst = (DiodeInstace *)inst_;
// set dc stamps
load_jacobian_resist(inst_, model);
// add reactive contributions
LOAD_MATRIX_TRAN(A_A)
LOAD_MATRIX_TRAN(A_CI)
LOAD_MATRIX_TRAN(CI_A)
LOAD_MATRIX_TRAN(CI_CI)
LOAD_MATRIX_TRAN(A_TNODE)
LOAD_MATRIX_TRAN(CI_TNODE)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
static void load_spice_rhs_dc(void *inst_, void *model, double *dst,
double *prev_solve) {
IGNORE(model);
DiodeInstace *inst = (DiodeInstace *)inst_;
double va = prev_solve[inst->node_off[A]];
double vci = prev_solve[inst->node_off[CI]];
double vc = prev_solve[inst->node_off[C]];
double vdtj = prev_solve[inst->node_off[TNODE]];
dst[inst->node_off[A]] += inst->jacobian_resist[A_A] * va +
inst->jacobian_resist[A_TNODE] * vdtj +
inst->jacobian_resist[A_CI] * vci +
inst->lim_rhs_resist_A - inst->residual_resist[A];
dst[inst->node_off[CI]] += inst->jacobian_resist[CI_A] * va +
inst->jacobian_resist[CI_TNODE] * vdtj +
inst->jacobian_resist[CI_CI] * vci +
inst->lim_rhs_resist_CI -
inst->residual_resist[CI];
dst[inst->node_off[C]] +=
inst->jacobian_resist[C_C] * vc + inst->jacobian_resist[C_CI] * vci +
inst->jacobian_resist[C_TNODE] * vdtj - inst->residual_resist[C];
dst[inst->node_off[TNODE]] += inst->jacobian_resist[TNODE_A] * va +
inst->jacobian_resist[TNODE_C] * vc +
inst->jacobian_resist[TNODE_CI] * vci +
inst->jacobian_resist[TNODE_TNODE] * vdtj -
inst->residual_resist[TNODE];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
static void load_spice_rhs_tran(void *inst_, void *model, double *dst,
double *prev_solve, double alpha) {
DiodeInstace *inst = (DiodeInstace *)inst_;
double va = prev_solve[inst->node_off[A]];
double vci = prev_solve[inst->node_off[CI]];
double vdtj = prev_solve[inst->node_off[TNODE]];
// set DC rhs
load_spice_rhs_dc(inst_, model, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[A]] +=
alpha *
(inst->jacobian_react[A_A] * va + inst->jacobian_react[A_CI] * vci +
inst->jacobian_react[A_TNODE] * vdtj + inst->lim_rhs_react_A);
dst[inst->node_off[CI]] +=
alpha *
(inst->jacobian_react[CI_CI] * vci + inst->jacobian_react[CI_A] * va +
inst->jacobian_react[CI_TNODE] * vdtj + inst->lim_rhs_react_CI);
}
#define RESIST_RESIDUAL_OFF(NODE) \
(offsetof(DiodeInstace, residual_resist) + sizeof(uint32_t) * NODE)
// structure that provides information of all nodes of the model
const OsdiNode nodes[NUM_NODES] = {
{
.name = "A",
.units = "V",
.residual_units = "A",
.resist_residual_off = RESIST_RESIDUAL_OFF(A),
.react_residual_off = offsetof(DiodeInstace, residual_react_A),
},
{
.name = "C",
.units = "V",
.residual_units = "A",
.resist_residual_off = RESIST_RESIDUAL_OFF(C),
.react_residual_off = UINT32_MAX, // no reactive residual
},
{
.name = "dT",
.units = "K",
.residual_units = "W",
.resist_residual_off = RESIST_RESIDUAL_OFF(TNODE),
.react_residual_off = UINT32_MAX, // no reactive residual
},
{
.name = "CI",
.units = "V",
.residual_units = "A",
.resist_residual_off = RESIST_RESIDUAL_OFF(TNODE),
.react_residual_off = offsetof(DiodeInstace, residual_react_CI),
},
};
#define JACOBI_ENTRY(N1, N2) \
{ \
.nodes = {N1, N2}, .flags = JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT, \
.react_ptr_off = offsetof(DiodeInstace, jacobian_ptr_react) + \
sizeof(double *) * N1##_##N2 \
}
#define RESIST_JACOBI_ENTRY(N1, N2) \
{ \
.nodes = {N1, N2}, .flags = JACOBIAN_ENTRY_RESIST, \
.react_ptr_off = UINT32_MAX \
}
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiJacobianEntry jacobian_entries[NUM_MATRIX] = {
JACOBI_ENTRY(CI, CI),
RESIST_JACOBI_ENTRY(CI, C),
RESIST_JACOBI_ENTRY(C, CI),
RESIST_JACOBI_ENTRY(C, C),
JACOBI_ENTRY(A, A),
JACOBI_ENTRY(A, CI),
JACOBI_ENTRY(CI, A),
JACOBI_ENTRY(A, TNODE),
RESIST_JACOBI_ENTRY(C, TNODE),
JACOBI_ENTRY(CI, TNODE),
RESIST_JACOBI_ENTRY(TNODE, TNODE),
RESIST_JACOBI_ENTRY(TNODE, A),
RESIST_JACOBI_ENTRY(TNODE, C),
RESIST_JACOBI_ENTRY(TNODE, CI),
};
OsdiNodePair collapsible[NUM_COLLAPSIBLE] = {
{CI, C},
{TNODE, NUM_NODES},
};
#define NUM_PARAMS 12
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"$mfactor"},
.num_alias = 0,
.description = "Verilog-A multiplication factor for parallel devices",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_INST,
.len = 0,
},
{
.name = (char *[]){"Rs"},
.num_alias = 0,
.description = "Ohmic res",
.units = "Ohm",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Is"},
.num_alias = 0,
.description = "Saturation current",
.units = "A",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetars"},
.num_alias = 0,
.description = "Temperature coefficient of ohmic res",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"N"},
.num_alias = 0,
.description = "Emission coefficient",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Cj0"},
.num_alias = 0,
.description = "Junction capacitance",
.units = "F",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Vj"},
.num_alias = 0,
.description = "Junction potential",
.units = "V",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"M"},
.num_alias = 0,
.description = "Grading coefficient",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Rth"},
.num_alias = 0,
.description = "Thermal resistance",
.units = "K/W",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetarth"},
.num_alias = 0,
.description = "Temperature coefficient of thermal res",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetais"},
.num_alias = 0,
.description = "Temperature coefficient of Is",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Tnom"},
.num_alias = 0,
.description = "Reference temperature",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "diode_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 3,
.nodes = (OsdiNode *)&nodes,
.node_mapping_offset = offsetof(DiodeInstace, node_off),
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiJacobianEntry *)&jacobian_entries,
.jacobian_ptr_resist_offset = offsetof(DiodeInstace, jacobian_ptr_resist),
// node collapsing
.num_collapsible = NUM_COLLAPSIBLE,
.collapsible = collapsible,
.collapsed_offset = offsetof(DiodeInstace, collapsed),
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 1,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// step size bound
.bound_step_offset = UINT32_MAX,
.num_states = 1,
.state_idx_off = offsetof(DiodeInstace, state_idx),
// memory
.instance_size = sizeof(DiodeInstace),
.model_size = sizeof(DiodeModel),
// setup
.access = osdi_access,
.setup_model = setup_model,
.setup_instance = setup_instance,
.eval = eval,
.load_noise = load_noise,
.load_residual_resist = load_residual_resist,
.load_residual_react = load_residual_react,
.load_spice_rhs_dc = load_spice_rhs_dc,
.load_spice_rhs_tran = load_spice_rhs_tran,
.load_jacobian_resist = load_jacobian_resist,
.load_jacobian_react = load_jacobian_react,
.load_jacobian_tran = load_jacobian_tran,
.load_limit_rhs_react = load_lim_rhs_react,
.load_limit_rhs_resist = load_lim_rhs_resist,
}};
OsdiLimFunction OSDI_LIM_TABLE[1] = {{.name = "pnjlim", .num_args = 2}};
uint32_t OSDI_LIM_TABLE_LEN = 1;

View File

@ -0,0 +1,46 @@
OSDI Diode Test
.options abstol=1e-15
* one voltage source for sweeping, one for sensing:
VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M)
Vsense Dx D DC 0
* Rt T 0 1e10 *not supported Pascal?
* model definitions:
.model dmod_built_in d( bv=5.0000000000e+01 is=1e-13 n=1.05 thermal=1 tnom=27 rth0=100 rs=5 cj0=1e-15 vj=0.5 m=0.6 )
.model dmod_osdi diode_va rs=5 is=1e-13 n=1.05 Rth=100 cj0=1e-15 vj=0.5 m=0.6
*OSDI Diode:
*OSDI_ACTIVATE*A1 D 0 T dmod_osdi
*Built-in Diode:
*BUILT_IN_ACTIVATE*D1 D 0 T dmod_built_in
.control
pre_osdi diode.osdi
set filetype=ascii
set wr_vecnames
set wr_singlescale
* a DC sweep from 0.3V to 1V
dc Vd 0.3 1.0 0.01
wrdata dc_sim.ngspice v(d) i(vsense) v(t)
* an AC sweep at Vd=0.5V
alter VD=0.5
ac dec 10 .01 10
wrdata ac_sim.ngspice v(d) i(vsense)
* a transient analysis
tran 100ms 500000ms
wrdata tr_sim.ngspice v(d) i(vsense)
* print number of iterations
rusage totiter
.endc
.end

View File

@ -0,0 +1,162 @@
""" test OSDI simulation of diode
"""
import os, shutil
import numpy as np
import pandas as pd
import sys
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from testing import prepare_test
# This test runs a DC, AC and Transient Simulation of a simple diode.
# The diode is available in the "OSDI" Git project and needs to be compiled to a shared object
# and then bet put into /usr/local/share/ngspice/osdi:
#
# > make osdi_diode
# > cp diode_osdi.osdi /usr/local/share/ngspice/osdi/diode_osdi.osdi
#
# The integration test proves the functioning of the OSDI interface. The Ngspice diode is quite
# complicated and the results are therefore not exactly the same.
# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation.
directory = os.path.dirname(__file__)
def test_ngspice():
dir_osdi, dir_built_in = prepare_test(directory)
# read DC simulation results
dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
dc_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\\s+"
)
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# read AC simulation results
ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
ac_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\\s+"
)
# read TR simulation results
tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
tr_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\\s+"
)
# test simulation results
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
np.testing.assert_allclose(id_osdi[20:40], id_built_in[20:40], rtol=0.03)
return (
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
)
if __name__ == "__main__":
(
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
) = test_ngspice()
import matplotlib.pyplot as plt
# DC Plot
pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"]
pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.semilogy(
dc_data_built_in["v(d)"],
dc_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
ax1.semilogy(
dc_data_osdi["v(d)"],
dc_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
ax2.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["v(t)"],
label="built-in",
linestyle=" ",
marker="x",
)
ax2.plot(
dc_data_osdi["v(d)"],
dc_data_osdi["v(t)"],
label="OSDI",
)
ax1.set_ylabel(r"$I_{\mathrm{D}} (\mathrm{mA})$")
ax2.set_ylabel(r"$\Delta T_{\mathrm{j}}(\mathrm{K})$")
ax1.set_xlabel(r"$V_{\mathrm{D}}(\mathrm{V})$")
plt.legend()
# AC Plot
omega = 2 * np.pi * ac_data_osdi["frequency"]
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI"
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Re \\left\{ Y_{11} \\right\} (\\mathrm{mS})$")
plt.legend()
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense).1"] * 1e3 / omega,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"],
ac_data_osdi["i(vsense).1"] * 1e3 / omega,
label="OSDI",
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{mF})$")
plt.legend()
# TR plot
fig = plt.figure()
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.plot(
tr_data_osdi["time"] * 1e9,
tr_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
plt.xlabel(r"$t(\mathrm{nS})$")
plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$")
plt.legend()
plt.show()

Binary file not shown.

View File

View File

View File

View File

@ -0,0 +1,374 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 3
#define P 0
#define M 1
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 4
#define P_P 0
#define P_M 1
#define M_P 2
#define M_M 3
// The model structure for the diode
typedef struct CapacitorModel
{
double C;
bool C_given;
} CapacitorModel;
// The instace structure for the diode
typedef struct CapacitorInstance
{
double temperature;
double rhs_resist[NUM_NODES];
double rhs_react[NUM_NODES];
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
} CapacitorInstance;
// implementation of the access function as defined by the OSDI spec
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
{
CapacitorModel *model = (CapacitorModel *)model_;
CapacitorInstance *inst = (CapacitorInstance *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
value = (void *)&model->C;
given = &model->C_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET)
{
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
OsdiInitInfo setup_model(void *_handle, void *model_)
{
CapacitorModel *model = (CapacitorModel *)model_;
// set parameters and check bounds
if (!model->C_given)
{
model->C = 1e-15;
}
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_,
double temperature, uint32_t _num_terminals)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
CapacitorModel *model = (CapacitorModel *)model_;
inst->temperature = temperature;
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the eval function as defined in the OSDI spec
uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags,
double *prev_solve, OsdiSimParas *sim_params)
{
CapacitorModel *model = (CapacitorModel *)model_;
CapacitorInstance *inst = (CapacitorInstance *)inst_;
// get voltages
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
double vpm = vp - vm;
double gmin = 1e-12;
for (int i = 0; sim_params->names[i] != NULL; i++)
{
if (strcmp(sim_params->names[i], "gmin") == 0)
{
gmin = sim_params->vals[i];
}
}
double qc_vpm = model->C;
double qc = model->C * vpm;
////////////////////////////////
// evaluate model equations
////////////////////////////////
if (flags & CALC_REACT_RESIDUAL)
{
// write react rhs
inst->rhs_react[P] = qc;
inst->rhs_react[M] = -qc;
}
//////////////////
// write Jacobian
//////////////////
if (flags & CALC_REACT_JACOBIAN)
{
// write react matrix
// stamp Qd between nodes A and Ci depending also on dT
inst->jacobian_react[P_P] = qc_vpm;
inst->jacobian_react[P_M] = -qc_vpm;
inst->jacobian_react[M_P] = -qc_vpm;
inst->jacobian_react[M_M] = qc_vpm;
}
return 0;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens)
{
// TODO add noise to example
}
#define LOAD_RHS_RESIST(name) \
dst[inst->node_off[name]] += inst->rhs_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
void load_residual_resist(void *inst_, double *dst)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_RHS_RESIST(P)
LOAD_RHS_RESIST(M)
}
#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name];
// implementation of the load_rhs_react function as defined in the OSDI spec
void load_residual_react(void *inst_, double *dst)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_RHS_REACT(P)
LOAD_RHS_REACT(M)
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
void load_jacobian_resist(void *inst_)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_MATRIX_RESIST(P_P)
LOAD_MATRIX_RESIST(P_M)
LOAD_MATRIX_RESIST(M_P)
LOAD_MATRIX_RESIST(M_M)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
void load_jacobian_react(void *inst_, double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
LOAD_MATRIX_REACT(P_P)
LOAD_MATRIX_REACT(M_M)
LOAD_MATRIX_REACT(P_M)
LOAD_MATRIX_REACT(M_P)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
void load_jacobian_tran(void *inst_, double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
// set dc stamps
load_jacobian_resist(inst_);
// add reactive contributions
LOAD_MATRIX_TRAN(P_P)
LOAD_MATRIX_TRAN(M_M)
LOAD_MATRIX_TRAN(M_P)
LOAD_MATRIX_TRAN(M_M)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
dst[inst->node_off[P]] += inst->jacobian_resist[P_M] * vm +
inst->jacobian_resist[P_P] * vp -
inst->rhs_resist[P];
dst[inst->node_off[M]] += inst->jacobian_resist[M_P] * vp +
inst->jacobian_resist[M_M] * vm -
inst->rhs_resist[M];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve,
double alpha)
{
CapacitorInstance *inst = (CapacitorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
// set DC rhs
load_spice_rhs_dc(inst_, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[P]] +=
alpha * (inst->jacobian_react[P_P] * vp +
inst->jacobian_react[P_M] * vm);
dst[inst->node_off[M]] += alpha * (inst->jacobian_react[M_M] * vm +
inst->jacobian_react[M_P] * vp);
}
// structure that provides information of all nodes of the model
OsdiNode nodes[NUM_NODES] = {
{.name = "P", .units = "V", .is_reactive = true},
{.name = "M", .units = "V", .is_reactive = true},
};
// boolean array that tells which Jacobian entries are constant. Nothing is
// constant with selfheating, though.
bool const_jacobian_entries[NUM_MATRIX] = {};
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiNodePair jacobian_entries[NUM_MATRIX] = {
{P, P},
{P, M},
{M, P},
{M, M},
};
#define NUM_PARAMS 1
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"C"},
.num_alias = 0,
.description = "Capacitance",
.units = "Farad",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "capacitor_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 2,
.nodes = (OsdiNode *)&nodes,
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiNodePair *)&jacobian_entries,
.const_jacobian_entries = (bool *)&const_jacobian_entries,
// memory
.instance_size = sizeof(CapacitorInstance),
.model_size = sizeof(CapacitorModel),
.residual_resist_offset = offsetof(CapacitorInstance, rhs_resist),
.residual_react_offset = offsetof(CapacitorInstance, rhs_react),
.node_mapping_offset = offsetof(CapacitorInstance, node_off),
.jacobian_resist_offset = offsetof(CapacitorInstance, jacobian_resist),
.jacobian_react_offset = offsetof(CapacitorInstance, jacobian_react),
.jacobian_ptr_resist_offset = offsetof(CapacitorInstance, jacobian_ptr_resist),
.jacobian_ptr_react_offset = offsetof(CapacitorInstance, jacobian_ptr_react),
// TODO add node collapsing to example
// node collapsing
.num_collapsible = 0,
.collapsible = NULL,
.is_collapsible_offset = 0,
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 0,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// setup
.access = &osdi_access,
.setup_model = &setup_model,
.setup_instance = &setup_instance,
.eval = &eval,
.load_noise = &load_noise,
.load_residual_resist = &load_residual_resist,
.load_residual_react = &load_residual_react,
.load_spice_rhs_dc = &load_spice_rhs_dc,
.load_spice_rhs_tran = &load_spice_rhs_tran,
.load_jacobian_resist = &load_jacobian_resist,
.load_jacobian_react = &load_jacobian_react,
.load_jacobian_tran = &load_jacobian_tran,
}};

View File

@ -0,0 +1,49 @@
OSDI Multiple Devices Test
.options abstol=1e-15
* one voltage source for sweeping, one for sensing:
VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 10M)
Vsense Dx D DC 0
* model definitions:
.model rmod_osdi resistor_va r=20
.model cmod_osdi capacitor_va c=5
*OSDI Resistor and Capacitor:
*OSDI_ACTIVATE*A1 D 0 rmod_osdi
*OSDI_ACTIVATE*A2 D 0 rmod_osdi
*OSDI_ACTIVATE*A3 D 0 cmod_osdi
*Built-in Capacitor and Resistor:
*BUILT_IN_ACTIVATE*R1 D 0 20
*BUILT_IN_ACTIVATE*R2 D 0 20
*BUILT_IN_ACTIVATE*C1 D 0 5
.control
pre_osdi resistor.osdi capacitor.osdi
set filetype=ascii
set wr_vecnames
set wr_singlescale
* a DC sweep from 0.3V to 1V
dc Vd 0.3 1.0 0.01
wrdata dc_sim.ngspice v(d) i(vsense)
* an AC sweep at Vd=0.5V
alter VD=0.5
ac dec 10 .01 10
wrdata ac_sim.ngspice v(d) i(vsense)
* a transient analysis
tran 100ms 500000ms
wrdata tr_sim.ngspice v(d) i(vsense)
* print number of iterations
rusage totiter
.endc
.end

View File

@ -0,0 +1,364 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 3
#define P 0
#define M 1
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 4
#define P_P 0
#define P_M 1
#define M_P 2
#define M_M 3
// The model structure for the diode
typedef struct ResistorModel
{
double R;
bool R_given;
} ResistorModel;
// The instace structure for the diode
typedef struct ResistorInstance
{
double temperature;
double rhs_resist[NUM_NODES];
double rhs_react[NUM_NODES];
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
} ResistorInstance;
// implementation of the access function as defined by the OSDI spec
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
{
ResistorModel *model = (ResistorModel *)model_;
ResistorInstance *inst = (ResistorInstance *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
value = (void *)&model->R;
given = &model->R_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET)
{
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
OsdiInitInfo setup_model(void *_handle, void *model_)
{
ResistorModel *model = (ResistorModel *)model_;
// set parameters and check bounds
if (!model->R_given)
{
model->R = 1;
}
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_,
double temperature, uint32_t _num_terminals)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
ResistorModel *model = (ResistorModel *)model_;
inst->temperature = temperature;
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the eval function as defined in the OSDI spec
uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags,
double *prev_solve, OsdiSimParas *sim_params)
{
ResistorModel *model = (ResistorModel *)model_;
ResistorInstance *inst = (ResistorInstance *)inst_;
// get voltages
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
double vpm = vp - vm;
double ir = vpm / model->R;
double g = 1 / model->R;
////////////////
// write rhs
////////////////
if (flags & CALC_RESIST_RESIDUAL)
{
// write resist rhs
inst->rhs_resist[P] = ir;
inst->rhs_resist[M] = -ir;
}
//////////////////
// write Jacobian
//////////////////
if (flags & CALC_RESIST_JACOBIAN)
{
// stamp resistor
inst->jacobian_resist[P_P] = g;
inst->jacobian_resist[P_M] = -g;
inst->jacobian_resist[M_P] = -g;
inst->jacobian_resist[M_M] = g;
}
return 0;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens)
{
// TODO add noise to example
}
#define LOAD_RHS_RESIST(name) \
dst[inst->node_off[name]] += inst->rhs_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
void load_residual_resist(void *inst_, double *dst)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_RHS_RESIST(P)
LOAD_RHS_RESIST(M)
}
#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name];
// implementation of the load_rhs_react function as defined in the OSDI spec
void load_residual_react(void *inst_, double *dst)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_RHS_REACT(P)
LOAD_RHS_REACT(M)
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
void load_jacobian_resist(void *inst_)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_MATRIX_RESIST(P_P)
LOAD_MATRIX_RESIST(P_M)
LOAD_MATRIX_RESIST(M_P)
LOAD_MATRIX_RESIST(M_M)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
void load_jacobian_react(void *inst_, double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_MATRIX_REACT(P_P)
LOAD_MATRIX_REACT(M_M)
LOAD_MATRIX_REACT(P_M)
LOAD_MATRIX_REACT(M_P)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
void load_jacobian_tran(void *inst_, double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
// set dc stamps
load_jacobian_resist(inst_);
// add reactive contributions
LOAD_MATRIX_TRAN(P_P)
LOAD_MATRIX_TRAN(M_M)
LOAD_MATRIX_TRAN(M_P)
LOAD_MATRIX_TRAN(M_M)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
dst[inst->node_off[P]] += inst->jacobian_resist[P_M] * vm +
inst->jacobian_resist[P_P] * vp -
inst->rhs_resist[P];
dst[inst->node_off[M]] += inst->jacobian_resist[M_P] * vp +
inst->jacobian_resist[M_M] * vm -
inst->rhs_resist[M];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve,
double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
// set DC rhs
load_spice_rhs_dc(inst_, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[P]] +=
alpha * (inst->jacobian_react[P_P] * vp +
inst->jacobian_react[P_M] * vm);
dst[inst->node_off[M]] += alpha * (inst->jacobian_react[M_M] * vm +
inst->jacobian_react[M_P] * vp);
}
// structure that provides information of all nodes of the model
OsdiNode nodes[NUM_NODES] = {
{.name = "P", .units = "V", .is_reactive = false},
{.name = "M", .units = "V", .is_reactive = false},
};
// boolean array that tells which Jacobian entries are constant. Nothing is
// constant with selfheating, though.
bool const_jacobian_entries[NUM_MATRIX] = {};
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiNodePair jacobian_entries[NUM_MATRIX] = {
{P, P},
{P, M},
{M, P},
{M, M},
};
#define NUM_PARAMS 1
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"R"},
.num_alias = 0,
.description = "Resistance",
.units = "Ohm",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "resistor_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 2,
.nodes = (OsdiNode *)&nodes,
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiNodePair *)&jacobian_entries,
.const_jacobian_entries = (bool *)&const_jacobian_entries,
// memory
.instance_size = sizeof(ResistorInstance),
.model_size = sizeof(ResistorModel),
.residual_resist_offset = offsetof(ResistorInstance, rhs_resist),
.residual_react_offset = offsetof(ResistorInstance, rhs_react),
.node_mapping_offset = offsetof(ResistorInstance, node_off),
.jacobian_resist_offset = offsetof(ResistorInstance, jacobian_resist),
.jacobian_react_offset = offsetof(ResistorInstance, jacobian_react),
.jacobian_ptr_resist_offset = offsetof(ResistorInstance, jacobian_ptr_resist),
.jacobian_ptr_react_offset = offsetof(ResistorInstance, jacobian_ptr_react),
// TODO add node collapsing to example
// node collapsing
.num_collapsible = 0,
.collapsible = NULL,
.is_collapsible_offset = 0,
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 0,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// setup
.access = &osdi_access,
.setup_model = &setup_model,
.setup_instance = &setup_instance,
.eval = &eval,
.load_noise = &load_noise,
.load_residual_resist = &load_residual_resist,
.load_residual_react = &load_residual_react,
.load_spice_rhs_dc = &load_spice_rhs_dc,
.load_spice_rhs_tran = &load_spice_rhs_tran,
.load_jacobian_resist = &load_jacobian_resist,
.load_jacobian_react = &load_jacobian_react,
.load_jacobian_tran = &load_jacobian_tran,
}};

View File

@ -0,0 +1,172 @@
""" test OSDI simulation of resistor and capacitor
"""
import os, shutil
import numpy as np
import pandas as pd
import sys
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from testing import prepare_test
# This test runs a DC, AC and Transient Simulation of a simple resistor and resistor.
# The capacitor and resistor are available as C files and need to be compiled to a shared objects
# and then bet put into /usr/local/share/ngspice/osdi:
#
# > make osdi_resistor
# > cp resistor_osdi.osdi /usr/local/share/ngspice/osdi/resistor_osdi.osdi
# > make osdi_capacitor
# > cp capacitor_osdi.osdi /usr/local/share/ngspice/osdi/capacitor_osdi.osdi
#
# The integration test proves the functioning of the OSDI interface.
# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation.
directory = os.path.dirname(__file__)
def test_ngspice():
dir_osdi, dir_built_in = prepare_test(directory)
# read DC simulation results
dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
dc_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\\s+"
)
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# read AC simulation results
ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
ac_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\\s+"
)
# read TR simulation results
tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
tr_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\\s+"
)
# test simulation results
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# np.testing.assert_allclose(id_osdi[0:20], id_built_in[0:20], rtol=0.01)
return (
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
)
if __name__ == "__main__":
(
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
) = test_ngspice()
import matplotlib.pyplot as plt
# DC Plot
pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"]
pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"]
fig, ax1 = plt.subplots()
ax1.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
ax1.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["v(d)"] / 10 * 1e3,
label="analytical",
linestyle="--",
marker="s",
)
ax1.plot(
dc_data_osdi["v(d)"],
dc_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
plt.legend()
# AC Plot
omega = 2 * np.pi * ac_data_osdi["frequency"]
z_analytical = 1 / 10
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_built_in["frequency"],
np.ones_like(ac_data_built_in["frequency"]) * z_analytical * 1e3,
label="analytical",
linestyle="--",
marker="s",
)
plt.semilogx(
ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI"
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Re \\left\{ Y_{11} \\right\} (\\mathrm{mS})$")
plt.legend()
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense).1"] / omega,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_built_in["frequency"],
np.ones_like(ac_data_built_in["i(vsense).1"]) *5,
label="analytical",
linestyle="--",
marker="s",
)
plt.semilogx(
ac_data_osdi["frequency"],
ac_data_osdi["i(vsense).1"] / omega,
label="OSDI",
)
plt.ylim(4, 6)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{F})$")
plt.legend()
# TR plot
fig = plt.figure()
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.plot(
tr_data_osdi["time"] * 1e9,
tr_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
plt.xlabel(r"$t(\mathrm{nS})$")
plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$")
plt.legend()
plt.show()

View File

View File

@ -0,0 +1,831 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 4
#define A 0
#define C 1
#define TNODE 2
#define CI 3
#define NUM_COLLAPSIBLE 2
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 14
#define CI_CI 0
#define CI_C 1
#define C_CI 2
#define C_C 3
#define A_A 4
#define A_CI 5
#define CI_A 6
#define A_TNODE 7
#define C_TNODE 8
#define CI_TNODE 9
#define TNODE_TNODE 10
#define TNODE_A 11
#define TNODE_C 12
#define TNODE_CI 13
// The model structure for the diode
typedef struct DiodeModel
{
double Rs;
bool Rs_given;
double Is;
bool Is_given;
double zetars;
bool zetars_given;
double N;
bool N_given;
double Cj0;
bool Cj0_given;
double Vj;
bool Vj_given;
double M;
bool M_given;
double Rth;
bool Rth_given;
double zetarth;
bool zetarth_given;
double zetais;
bool zetais_given;
double Tnom;
bool Tnom_given;
double mfactor; // multiplication factor for parallel devices
bool mfactor_given;
// InitError errors[MAX_ERROR_NUM],
} DiodeModel;
// The instace structure for the diode
typedef struct DiodeInstace
{
double mfactor; // multiplication factor for parallel devices
bool mfactor_given;
double temperature;
double rhs_resist[NUM_NODES];
double rhs_react[NUM_NODES];
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
bool is_collapsible[NUM_COLLAPSIBLE];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
} DiodeInstace;
#define EXP_LIM 80.0
double limexp(double x)
{
if (x < EXP_LIM)
{
return exp(x);
}
else
{
return exp(EXP_LIM) * (x + 1 - EXP_LIM);
}
}
double dlimexp(double x)
{
if (x < EXP_LIM)
{
return exp(x);
}
else
{
return exp(EXP_LIM);
}
}
// implementation of the access function as defined by the OSDI spec
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
{
DiodeModel *model = (DiodeModel *)model_;
DiodeInstace *inst = (DiodeInstace *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
if (flags & ACCESS_FLAG_INSTANCE)
{
value = (void *)&inst->mfactor;
given = &inst->mfactor_given;
}
else
{
value = (void *)&model->mfactor;
given = &model->mfactor_given;
}
break;
case 1:
value = (void *)&model->Rs;
given = &model->Rs_given;
break;
case 2:
value = (void *)&model->Is;
given = &model->Is_given;
break;
case 3:
value = (void *)&model->zetars;
given = &model->zetars_given;
break;
case 4:
value = (void *)&model->N;
given = &model->N_given;
break;
case 5:
value = (void *)&model->Cj0;
given = &model->Cj0_given;
break;
case 6:
value = (void *)&model->Vj;
given = &model->Vj_given;
break;
case 7:
value = (void *)&model->M;
given = &model->M_given;
break;
case 8:
value = &model->Rth;
given = &model->Rth_given;
break;
case 9:
value = (void *)&model->zetarth;
given = &model->zetarth_given;
break;
case 10:
value = (void *)&model->zetais;
given = &model->zetais_given;
break;
case 11:
value = (void *)&model->Tnom;
given = &model->Tnom_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET)
{
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
OsdiInitInfo setup_model(void *_handle, void *model_)
{
DiodeModel *model = (DiodeModel *)model_;
// set parameters and check bounds
if (!model->mfactor_given)
{
model->mfactor = 1.0;
}
if (!model->Rs_given)
{
model->Rs = 1e-9;
}
if (!model->Is_given)
{
model->Is = 1e-14;
}
if (!model->zetars_given)
{
model->zetars = 0;
}
if (!model->N_given)
{
model->N = 1;
}
if (!model->Cj0_given)
{
model->Cj0 = 0;
}
if (!model->Vj_given)
{
model->Vj = 1.0;
}
if (!model->M_given)
{
model->M = 0.5;
}
if (!model->Rth_given)
{
model->Rth = 0;
}
if (!model->zetarth_given)
{
model->zetarth = 0;
}
if (!model->zetais_given)
{
model->zetais = 0;
}
if (!model->Tnom_given)
{
model->Tnom = 300;
}
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_,
double temperature, uint32_t _num_terminals)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
DiodeModel *model = (DiodeModel *)model_;
// Here the logic for node collapsing ist implemented. The indices in this list must adhere to the "collapsible" List of node pairs.
if (model->Rs<1e-9){ // Rs between Ci C
inst->is_collapsible[0] = true;
}
if (model->Rth<1e-9){ // Rs between Ci C
inst->is_collapsible[1] = true;
}
if (!inst->mfactor_given)
{
if (model->mfactor_given)
{
inst->mfactor = model->mfactor;
}
else
{
inst->mfactor = 1;
}
}
inst->temperature = temperature;
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the eval function as defined in the OSDI spec
uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags,
double *prev_solve, OsdiSimParas *sim_params)
{
DiodeModel *model = (DiodeModel *)model_;
DiodeInstace *inst = (DiodeInstace *)inst_;
// get voltages
double va = prev_solve[inst->node_off[A]];
double vc = prev_solve[inst->node_off[C]];
double vci = prev_solve[inst->node_off[CI]];
double vdtj = prev_solve[inst->node_off[TNODE]];
double vcic = vci - vc;
double vaci = va - vci;
double gmin = 1e-12;
for (int i = 0; sim_params->names[i] != NULL; i++)
{
if (strcmp(sim_params->names[i], "gmin") == 0)
{
gmin = sim_params->vals[i];
}
}
////////////////////////////////
// evaluate model equations
////////////////////////////////
// temperature update
double pk = 1.3806503e-23;
double pq = 1.602176462e-19;
double t_dev = inst->temperature + vdtj;
double tdev_tnom = t_dev / model->Tnom;
double rs_t = model->Rs * powf(tdev_tnom, model->zetars);
double rth_t = model->Rth * powf(tdev_tnom, model->zetarth);
double is_t = model->Is * powf(tdev_tnom, model->zetais);
double vt = t_dev * pk / pq;
// derivatives w.r.t. temperature
double rs_dt = model->zetars * model->Rs *
powf(tdev_tnom, model->zetars - 1.0) / model->Tnom;
double rth_dt = model->zetarth * model->Rth *
powf(tdev_tnom, model->zetarth - 1.0) / model->Tnom;
double is_dt = model->zetais * model->Is *
powf(tdev_tnom, model->zetais - 1.0) / model->Tnom;
double vt_tj = pk / pq;
// evaluate model equations and calculate all derivatives
// diode current
double id = is_t * (limexp(vaci / (model->N * vt)) - 1.0);
double gd = is_t / vt * dlimexp(vaci / (model->N * vt));
double gdt = -is_t * dlimexp(vaci / (model->N * vt)) * vaci / model->N / vt /
vt * vt_tj +
1.0 * exp((vaci / (model->N * vt)) - 1.0) * is_dt;
// resistor
double irs = 0;
double g = 0;
double grt = 0;
if (!inst->is_collapsible[0]) {
irs = vcic / rs_t;
g = 1.0 / rs_t;
grt = -irs / rs_t * rs_dt;
}
// thermal resistance
double irth = 0;
double gt = 0;
if (!inst->is_collapsible[1]) {
irth = vdtj / rth_t;
gt = 1.0 / rth_t - irth / rth_t * rth_dt;
}
// charge
double vf = model->Vj * (1.0 - powf(3.04, -1.0 / model->M));
double x = (vf - vaci) / vt;
double x_vt = -x / vt;
double x_dtj = x_vt * vt_tj;
double x_vaci = -1.0 / vt;
double y = sqrt(x * x + 1.92);
double y_x = 0.5 / y * 2.0 * x;
double y_vaci = y_x * x_vaci;
double y_dtj = y_x * x_dtj;
double vd = vf - vt * (x + y) / (2.0);
double vd_x = -vt / 2.0;
double vd_y = -vt / 2.0;
double vd_vt = -(x + y) / (2.0);
double vd_dtj = vd_x * x_dtj + vd_y * y_dtj + vd_vt * vt_tj;
double vd_vaci = vd_x * x_vaci + vd_y * y_vaci;
double qd = model->Cj0 * vaci * model->Vj *
(1.0 - powf(1.0 - vd / model->Vj, 1.0 - model->M)) /
(1.0 - model->M);
double qd_vd = model->Cj0 * model->Vj / (1.0 - model->M) * (1.0 - model->M) *
powf(1.0 - vd / model->Vj, 1.0 - model->M - 1.0) / model->Vj;
double qd_dtj = qd_vd * vd_dtj;
double qd_vaci = qd_vd * vd_vaci;
// thermal power source = current source
double ith = id * vaci ;
double ith_vtj = gdt * vaci ;
double ith_vcic = 0;
double ith_vaci = gd * vaci + id;
if (!inst->is_collapsible[0]) {
ith_vcic = 2.0 * vcic / rs_t;
ith += powf(vcic, 2.0) / rs_t;
ith_vtj -= - powf(vcic, 2.0) / rs_t / rs_t * rs_dt;
}
id += gmin * vaci;
gd += gmin;
double mfactor = inst->mfactor;
////////////////
// write rhs
////////////////
if (flags & CALC_RESIST_RESIDUAL)
{
// write resist rhs
inst->rhs_resist[A] = id * mfactor;
inst->rhs_resist[CI] = -id * mfactor + irs * mfactor;
inst->rhs_resist[C] = -irs * mfactor;
inst->rhs_resist[TNODE] = -ith * mfactor + irth * mfactor;
}
if (flags & CALC_REACT_RESIDUAL)
{
// write react rhs
inst->rhs_react[A] = qd * mfactor;
inst->rhs_react[CI] = -qd * mfactor;
}
//////////////////
// write Jacobian
//////////////////
if (flags & CALC_RESIST_JACOBIAN)
{
// stamp diode (current flowing from Ci into A)
inst->jacobian_resist[A_A] = gd * mfactor;
inst->jacobian_resist[A_CI] = -gd * mfactor;
inst->jacobian_resist[CI_A] = -gd * mfactor;
inst->jacobian_resist[CI_CI] = gd * mfactor;
// diode thermal
inst->jacobian_resist[A_TNODE] = gdt * mfactor;
inst->jacobian_resist[CI_TNODE] = -gdt * mfactor;
// stamp resistor (current flowing from C into CI)
inst->jacobian_resist[CI_CI] += g * mfactor;
inst->jacobian_resist[CI_C] = -g * mfactor;
inst->jacobian_resist[C_CI] = -g * mfactor;
inst->jacobian_resist[C_C] = g * mfactor;
// resistor thermal
inst->jacobian_resist[CI_TNODE] = grt * mfactor;
inst->jacobian_resist[C_TNODE] = -grt * mfactor;
// stamp rth flowing into node dTj
inst->jacobian_resist[TNODE_TNODE] = gt * mfactor;
// stamp ith flowing out of T node
inst->jacobian_resist[TNODE_TNODE] -= ith_vtj * mfactor;
inst->jacobian_resist[TNODE_CI] = (ith_vcic - ith_vaci) * mfactor;
inst->jacobian_resist[TNODE_C] = -ith_vcic * mfactor;
inst->jacobian_resist[TNODE_A] = ith_vaci * mfactor;
}
if (flags & CALC_REACT_JACOBIAN)
{
// write react matrix
// stamp Qd between nodes A and Ci depending also on dT
inst->jacobian_react[A_A] = qd_vaci * mfactor;
inst->jacobian_react[A_CI] = -qd_vaci * mfactor;
inst->jacobian_react[CI_A] = -qd_vaci * mfactor;
inst->jacobian_react[CI_CI] = qd_vaci * mfactor;
inst->jacobian_react[A_TNODE] = qd_dtj * mfactor;
inst->jacobian_react[CI_TNODE] = -qd_dtj * mfactor;
}
return 0;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens)
{
// TODO add noise to example
}
#define LOAD_RHS_RESIST(name) \
dst[inst->node_off[name]] += inst->rhs_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
void load_residual_resist(void *inst_, double *dst)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_RHS_RESIST(A)
LOAD_RHS_RESIST(CI)
LOAD_RHS_RESIST(C)
LOAD_RHS_RESIST(TNODE)
}
#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name];
// implementation of the load_rhs_react function as defined in the OSDI spec
void load_residual_react(void *inst_, double *dst)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_RHS_REACT(A)
LOAD_RHS_REACT(CI)
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
void load_jacobian_resist(void *inst_)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_MATRIX_RESIST(A_A)
LOAD_MATRIX_RESIST(A_CI)
LOAD_MATRIX_RESIST(A_TNODE)
LOAD_MATRIX_RESIST(CI_A)
LOAD_MATRIX_RESIST(CI_CI)
LOAD_MATRIX_RESIST(CI_C)
LOAD_MATRIX_RESIST(CI_TNODE)
LOAD_MATRIX_RESIST(C_CI)
LOAD_MATRIX_RESIST(C_C)
LOAD_MATRIX_RESIST(C_TNODE)
LOAD_MATRIX_RESIST(TNODE_TNODE)
LOAD_MATRIX_RESIST(TNODE_A)
LOAD_MATRIX_RESIST(TNODE_C)
LOAD_MATRIX_RESIST(TNODE_CI)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
void load_jacobian_react(void *inst_, double alpha)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
LOAD_MATRIX_REACT(A_A)
LOAD_MATRIX_REACT(A_CI)
LOAD_MATRIX_REACT(CI_A)
LOAD_MATRIX_REACT(CI_CI)
LOAD_MATRIX_REACT(A_TNODE)
LOAD_MATRIX_REACT(CI_TNODE)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
void load_jacobian_tran(void *inst_, double alpha)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
// set dc stamps
load_jacobian_resist(inst_);
// add reactive contributions
LOAD_MATRIX_TRAN(A_A)
LOAD_MATRIX_TRAN(A_CI)
LOAD_MATRIX_TRAN(CI_A)
LOAD_MATRIX_TRAN(CI_CI)
LOAD_MATRIX_TRAN(A_TNODE)
LOAD_MATRIX_TRAN(CI_TNODE)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
double va = prev_solve[inst->node_off[A]];
double vci = prev_solve[inst->node_off[CI]];
double vc = prev_solve[inst->node_off[C]];
double vdtj = prev_solve[inst->node_off[TNODE]];
dst[inst->node_off[A]] += inst->jacobian_resist[A_A] * va +
inst->jacobian_resist[A_TNODE] * vdtj +
inst->jacobian_resist[A_CI] * vci -
inst->rhs_resist[A];
dst[inst->node_off[CI]] += inst->jacobian_resist[CI_A] * va +
inst->jacobian_resist[CI_TNODE] * vdtj +
inst->jacobian_resist[CI_CI] * vci -
inst->rhs_resist[CI];
dst[inst->node_off[C]] += inst->jacobian_resist[C_C] * vc +
inst->jacobian_resist[C_CI] * vci +
inst->jacobian_resist[C_TNODE] * vdtj -
inst->rhs_resist[C];
dst[inst->node_off[TNODE]] += inst->jacobian_resist[TNODE_A] * va +
inst->jacobian_resist[TNODE_C] * vc +
inst->jacobian_resist[TNODE_CI] * vci +
inst->jacobian_resist[TNODE_TNODE] * vdtj -
inst->rhs_resist[TNODE];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve,
double alpha)
{
DiodeInstace *inst = (DiodeInstace *)inst_;
double va = prev_solve[inst->node_off[A]];
double vci = prev_solve[inst->node_off[CI]];
double vdtj = prev_solve[inst->node_off[TNODE]];
// set DC rhs
load_spice_rhs_dc(inst_, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[A]] +=
alpha * (inst->jacobian_react[A_A] * va +
inst->jacobian_react[A_CI] * vci +
inst->jacobian_react[A_TNODE] * vdtj);
dst[inst->node_off[CI]] += alpha * (inst->jacobian_react[CI_CI] * vci +
inst->jacobian_react[CI_A] * va +
inst->jacobian_react[CI_TNODE] * vdtj);
}
// structure that provides information of all nodes of the model
OsdiNode nodes[NUM_NODES] = {
{.name = "A", .units = "V", .is_reactive = true},
{.name = "C", .units = "V"},
{.name = "dT", .units = "K"},
{.name = "CI", .units = "V", .is_reactive = true},
};
// boolean array that tells which Jacobian entries are constant. Nothing is
// constant with selfheating, though.
bool const_jacobian_entries[NUM_MATRIX] = {};
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiNodePair jacobian_entries[NUM_MATRIX] = {
{CI, CI},
{CI, C},
{C, CI},
{C, C},
{A, A},
{A, CI},
{CI, A},
{A, TNODE},
{C, TNODE},
{CI, TNODE},
{TNODE, TNODE},
{TNODE, A},
{TNODE, C},
{TNODE, CI},
};
OsdiNodePair collapsible[NUM_COLLAPSIBLE] = {
{CI, C},
{TNODE, NUM_NODES},
};
#define NUM_PARAMS 12
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"$mfactor"},
.num_alias = 0,
.description = "Verilog-A multiplication factor for parallel devices",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_INST,
.len = 0,
},
{
.name = (char *[]){"Rs"},
.num_alias = 0,
.description = "Ohmic res",
.units = "Ohm",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Is"},
.num_alias = 0,
.description = "Saturation current",
.units = "A",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetars"},
.num_alias = 0,
.description = "Temperature coefficient of ohmic res",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"N"},
.num_alias = 0,
.description = "Emission coefficient",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Cj0"},
.num_alias = 0,
.description = "Junction capacitance",
.units = "F",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Vj"},
.num_alias = 0,
.description = "Junction potential",
.units = "V",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"M"},
.num_alias = 0,
.description = "Grading coefficient",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Rth"},
.num_alias = 0,
.description = "Thermal resistance",
.units = "K/W",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetarth"},
.num_alias = 0,
.description = "Temperature coefficient of thermal res",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"zetais"},
.num_alias = 0,
.description = "Temperature coefficient of Is",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
{
.name = (char *[]){"Tnom"},
.num_alias = 0,
.description = "Reference temperature",
.units = "",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "diode_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 3,
.nodes = (OsdiNode *)&nodes,
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiNodePair *)&jacobian_entries,
.const_jacobian_entries = (bool *)&const_jacobian_entries,
// memory
.instance_size = sizeof(DiodeInstace),
.model_size = sizeof(DiodeModel),
.residual_resist_offset = offsetof(DiodeInstace, rhs_resist),
.residual_react_offset = offsetof(DiodeInstace, rhs_react),
.node_mapping_offset = offsetof(DiodeInstace, node_off),
.jacobian_resist_offset = offsetof(DiodeInstace, jacobian_resist),
.jacobian_react_offset = offsetof(DiodeInstace, jacobian_react),
.jacobian_ptr_resist_offset = offsetof(DiodeInstace, jacobian_ptr_resist),
.jacobian_ptr_react_offset = offsetof(DiodeInstace, jacobian_ptr_react),
// node collapsing
.num_collapsible = NUM_COLLAPSIBLE,
.collapsible = collapsible,
.is_collapsible_offset = offsetof(DiodeInstace, is_collapsible),
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 1,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// setup
.access = &osdi_access,
.setup_model = &setup_model,
.setup_instance = &setup_instance,
.eval = &eval,
.load_noise = &load_noise,
.load_residual_resist = &load_residual_resist,
.load_residual_react = &load_residual_react,
.load_spice_rhs_dc = &load_spice_rhs_dc,
.load_spice_rhs_tran = &load_spice_rhs_tran,
.load_jacobian_resist = &load_jacobian_resist,
.load_jacobian_react = &load_jacobian_react,
.load_jacobian_tran = &load_jacobian_tran,
}};

View File

@ -0,0 +1,46 @@
OSDI Diode Test
.options abstol=1e-15
* one voltage source for sweeping, one for sensing:
VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M)
Vsense Dx D DC 0
* Rt T 0 1e10 *not supported Pascal?
* model definitions:
.model dmod_built_in d( bv=5.0000000000e+01 is=1e-13 n=1.05 thermal=1 tnom=27 rth0=1 rs=0 cj0=1e-15 vj=0.5 m=0.6 )
.model dmod_osdi diode_va rs=0 is=1e-13 n=1.05 Rth=0 cj0=1e-15 vj=0.5 m=0.6
*OSDI Diode:
*OSDI_ACTIVATE*A1 D 0 dmod_osdi
*Built-in Diode:
*BUILT_IN_ACTIVATE*D1 D 0 T dmod_built_in
.control
pre_osdi diode.osdi
set filetype=ascii
set wr_vecnames
set wr_singlescale
* a DC sweep from 0.3V to 1V
dc Vd 0.3 0.6 0.01
wrdata dc_sim.ngspice v(d) i(vsense)
* an AC sweep at Vd=0.5V
alter VD=0.5
ac dec 10 .01 10
wrdata ac_sim.ngspice v(d) i(vsense)
* a transient analysis
tran 100ms 500000ms
wrdata tr_sim.ngspice v(d) i(vsense)
* print number of iterations
rusage totiter
.endc
.end

View File

@ -0,0 +1,148 @@
""" test OSDI simulation of diode
"""
import os, shutil
import numpy as np
import pandas as pd
import sys
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from testing import prepare_test
# This test runs a DC, AC and Transient Simulation of a simple diode.
# The diode is available in the "OSDI" Git project and needs to be compiled to a shared object
# and then bet put into /usr/local/share/ngspice/osdi:
#
# > make osdi_diode
# > cp diode_osdi.osdi /usr/local/share/ngspice/osdi/diode_osdi.osdi
#
# The integration test proves the functioning of the OSDI interface. The Ngspice diode is quite
# complicated and the results are therefore not exactly the same.
# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation.
directory = os.path.dirname(__file__)
def test_ngspice():
dir_osdi, dir_built_in = prepare_test(directory)
# read DC simulation results
dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
dc_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\\s+"
)
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# read AC simulation results
ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
ac_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\\s+"
)
# read TR simulation results
tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
tr_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\\s+"
)
# test simulation results
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# np.testing.assert_allclose(id_osdi[0:20], id_built_in[0:20], rtol=0.01) #ngspice diode doesnt work with node collapsing :D
return (
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
)
if __name__ == "__main__":
(
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
) = test_ngspice()
import matplotlib.pyplot as plt
# DC Plot
pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"]
pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"]
fig, ax1 = plt.subplots()
ax1.semilogy(
dc_data_built_in["v(d)"],
dc_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
ax1.semilogy(
dc_data_osdi["v(d)"],
dc_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
ax1.set_ylabel(r"$I_{\mathrm{D}} (\mathrm{mA})$")
ax1.set_xlabel(r"$V_{\mathrm{D}}(\mathrm{V})$")
plt.legend()
# AC Plot
omega = 2 * np.pi * ac_data_osdi["frequency"]
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI"
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Re \\left\{ Y_{11} \\right\} (\\mathrm{mS})$")
plt.legend()
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense).1"] * 1e3 / omega,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_osdi["frequency"],
ac_data_osdi["i(vsense).1"] * 1e3 / omega,
label="OSDI",
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{mF})$")
plt.legend()
# TR plot
fig = plt.figure()
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.plot(
tr_data_osdi["time"] * 1e9,
tr_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
plt.xlabel(r"$t(\mathrm{nS})$")
plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$")
plt.legend()
plt.show()

View File

View File

@ -0,0 +1,44 @@
OSDI Resistor Test
.options abstol=1e-15
* one voltage source for sweeping, one for sensing:
VD Dx 0 DC 0 AC 1 SIN (0.5 0.2 1M)
Vsense Dx D DC 0
* model definitions:
.model rmod_osdi resistor_va r=10
*OSDI Resistor:
*OSDI_ACTIVATE*A1 D 0 rmod_osdi
*Built-in Resistor:
*BUILT_IN_ACTIVATE*R1 D 0 10
.control
pre_osdi resistor.osdi
set filetype=ascii
set wr_vecnames
set wr_singlescale
* a DC sweep from 0.3V to 1V
dc Vd 0.3 1.0 0.01
wrdata dc_sim.ngspice v(d) i(vsense)
* an AC sweep at Vd=0.5V
alter VD=0.5
ac dec 10 .01 10
wrdata ac_sim.ngspice v(d) i(vsense)
* a transient analysis
tran 100ms 500000ms
wrdata tr_sim.ngspice v(d) i(vsense)
* print number of iterations
rusage totiter
.endc
.end

View File

@ -0,0 +1,364 @@
/*
* This file is part of the OSDI component of NGSPICE.
* Copyright© 2022 SemiMod GmbH.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Author: Pascal Kuthe <pascal.kuthe@semimod.de>
*
* This is an exemplary implementation of the OSDI interface for the Verilog-A
* model specified in diode.va. In the future, the OpenVAF compiler shall
* generate an comparable object file. Primary purpose of this is example to
* have a concrete example for the OSDI interface, OpenVAF will generate a more
* optimized implementation.
*
*/
#include "osdi.h"
#include "string.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// public interface
extern uint32_t OSDI_VERSION_MAJOR;
extern uint32_t OSDI_VERSION_MINOR;
extern uint32_t OSDI_NUM_DESCRIPTORS;
extern OsdiDescriptor OSDI_DESCRIPTORS[1];
// number of nodes and definitions of node ids for nicer syntax in this file
// note: order should be same as "nodes" list defined later
#define NUM_NODES 2
#define P 0
#define M 1
// number of matrix entries and definitions for Jacobian entries for nicer
// syntax in this file
#define NUM_MATRIX 4
#define P_P 0
#define P_M 1
#define M_P 2
#define M_M 3
// The model structure for the diode
typedef struct ResistorModel
{
double R;
bool R_given;
} ResistorModel;
// The instace structure for the diode
typedef struct ResistorInstance
{
double temperature;
double rhs_resist[NUM_NODES];
double rhs_react[NUM_NODES];
double jacobian_resist[NUM_MATRIX];
double jacobian_react[NUM_MATRIX];
double *jacobian_ptr_resist[NUM_MATRIX];
double *jacobian_ptr_react[NUM_MATRIX];
uint32_t node_off[NUM_NODES];
} ResistorInstance;
// implementation of the access function as defined by the OSDI spec
void *osdi_access(void *inst_, void *model_, uint32_t id, uint32_t flags)
{
ResistorModel *model = (ResistorModel *)model_;
ResistorInstance *inst = (ResistorInstance *)inst_;
bool *given;
void *value;
switch (id) // id of params defined in param_opvar array
{
case 0:
value = (void *)&model->R;
given = &model->R_given;
break;
default:
return NULL;
}
if (flags & ACCESS_FLAG_SET)
{
*given = true;
}
return value;
}
// implementation of the setup_model function as defined in the OSDI spec
OsdiInitInfo setup_model(void *_handle, void *model_)
{
ResistorModel *model = (ResistorModel *)model_;
// set parameters and check bounds
if (!model->R_given)
{
model->R = 1;
}
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the setup_instace function as defined in the OSDI spec
OsdiInitInfo setup_instance(void *_handle, void *inst_, void *model_,
double temperature, uint32_t _num_terminals)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
ResistorModel *model = (ResistorModel *)model_;
inst->temperature = temperature;
return (OsdiInitInfo){.flags = 0, .num_errors = 0, .errors = NULL};
}
// implementation of the eval function as defined in the OSDI spec
uint32_t eval(void *handle, void *inst_, void *model_, uint32_t flags,
double *prev_solve, OsdiSimParas *sim_params)
{
ResistorModel *model = (ResistorModel *)model_;
ResistorInstance *inst = (ResistorInstance *)inst_;
// get voltages
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
double vpm = vp - vm;
double ir = vpm / model->R;
double g = 1 / model->R;
////////////////
// write rhs
////////////////
if (flags & CALC_RESIST_RESIDUAL)
{
// write resist rhs
inst->rhs_resist[P] = ir;
inst->rhs_resist[M] = -ir;
}
//////////////////
// write Jacobian
//////////////////
if (flags & CALC_RESIST_JACOBIAN)
{
// stamp resistor
inst->jacobian_resist[P_P] = g;
inst->jacobian_resist[P_M] = -g;
inst->jacobian_resist[M_P] = -g;
inst->jacobian_resist[M_M] = g;
}
return 0;
}
// TODO implementation of the load_noise function as defined in the OSDI spec
void load_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens)
{
// TODO add noise to example
}
#define LOAD_RHS_RESIST(name) \
dst[inst->node_off[name]] += inst->rhs_resist[name];
// implementation of the load_rhs_resist function as defined in the OSDI spec
void load_residual_resist(void *inst_, double *dst)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_RHS_RESIST(P)
LOAD_RHS_RESIST(M)
}
#define LOAD_RHS_REACT(name) dst[inst->node_off[name]] += inst->rhs_react[name];
// implementation of the load_rhs_react function as defined in the OSDI spec
void load_residual_react(void *inst_, double *dst)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_RHS_REACT(P)
LOAD_RHS_REACT(M)
}
#define LOAD_MATRIX_RESIST(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_resist[name];
// implementation of the load_matrix_resist function as defined in the OSDI spec
void load_jacobian_resist(void *inst_)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_MATRIX_RESIST(P_P)
LOAD_MATRIX_RESIST(P_M)
LOAD_MATRIX_RESIST(M_P)
LOAD_MATRIX_RESIST(M_M)
}
#define LOAD_MATRIX_REACT(name) \
*inst->jacobian_ptr_react[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_react function as defined in the OSDI spec
void load_jacobian_react(void *inst_, double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
LOAD_MATRIX_REACT(P_P)
LOAD_MATRIX_REACT(M_M)
LOAD_MATRIX_REACT(P_M)
LOAD_MATRIX_REACT(M_P)
}
#define LOAD_MATRIX_TRAN(name) \
*inst->jacobian_ptr_resist[name] += inst->jacobian_react[name] * alpha;
// implementation of the load_matrix_tran function as defined in the OSDI spec
void load_jacobian_tran(void *inst_, double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
// set dc stamps
load_jacobian_resist(inst_);
// add reactive contributions
LOAD_MATRIX_TRAN(P_P)
LOAD_MATRIX_TRAN(M_M)
LOAD_MATRIX_TRAN(M_P)
LOAD_MATRIX_TRAN(M_M)
}
// implementation of the load_spice_rhs_dc function as defined in the OSDI spec
void load_spice_rhs_dc(void *inst_, double *dst, double *prev_solve)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
dst[inst->node_off[P]] += inst->jacobian_resist[P_M] * vm +
inst->jacobian_resist[P_P] * vp -
inst->rhs_resist[P];
dst[inst->node_off[M]] += inst->jacobian_resist[M_P] * vp +
inst->jacobian_resist[M_M] * vm -
inst->rhs_resist[M];
}
// implementation of the load_spice_rhs_tran function as defined in the OSDI
// spec
void load_spice_rhs_tran(void *inst_, double *dst, double *prev_solve,
double alpha)
{
ResistorInstance *inst = (ResistorInstance *)inst_;
double vp = prev_solve[inst->node_off[P]];
double vm = prev_solve[inst->node_off[M]];
// set DC rhs
load_spice_rhs_dc(inst_, dst, prev_solve);
// add contributions due to reactive elements
dst[inst->node_off[P]] +=
alpha * (inst->jacobian_react[P_P] * vp +
inst->jacobian_react[P_M] * vm);
dst[inst->node_off[M]] += alpha * (inst->jacobian_react[M_M] * vm +
inst->jacobian_react[M_P] * vp);
}
// structure that provides information of all nodes of the model
OsdiNode nodes[NUM_NODES] = {
{.name = "P", .units = "V", .is_reactive = false},
{.name = "M", .units = "V", .is_reactive = false},
};
// boolean array that tells which Jacobian entries are constant. Nothing is
// constant with selfheating, though.
bool const_jacobian_entries[NUM_MATRIX] = {};
// these node pairs specify which entries in the Jacobian must be accounted for
OsdiNodePair jacobian_entries[NUM_MATRIX] = {
{P, P},
{P, M},
{M, P},
{M, M},
};
#define NUM_PARAMS 1
// the model parameters as defined in Verilog-A, bounds and default values are
// stored elsewhere as they may depend on model parameters etc.
OsdiParamOpvar params[NUM_PARAMS] = {
{
.name = (char *[]){"R"},
.num_alias = 0,
.description = "Resistance",
.units = "Ohm",
.flags = PARA_TY_REAL | PARA_KIND_MODEL,
.len = 0,
},
};
// fill exported data
uint32_t OSDI_VERSION_MAJOR = OSDI_VERSION_MAJOR_CURR;
uint32_t OSDI_VERSION_MINOR = OSDI_VERSION_MINOR_CURR;
uint32_t OSDI_NUM_DESCRIPTORS = 1;
// this is the main structure used by simulators, it gives access to all
// information in a model
OsdiDescriptor OSDI_DESCRIPTORS[1] = {{
// metadata
.name = "resistor_va",
// nodes
.num_nodes = NUM_NODES,
.num_terminals = 2,
.nodes = (OsdiNode *)&nodes,
// matrix entries
.num_jacobian_entries = NUM_MATRIX,
.jacobian_entries = (OsdiNodePair *)&jacobian_entries,
.const_jacobian_entries = (bool *)&const_jacobian_entries,
// memory
.instance_size = sizeof(ResistorInstance),
.model_size = sizeof(ResistorModel),
.residual_resist_offset = offsetof(ResistorInstance, rhs_resist),
.residual_react_offset = offsetof(ResistorInstance, rhs_react),
.node_mapping_offset = offsetof(ResistorInstance, node_off),
.jacobian_resist_offset = offsetof(ResistorInstance, jacobian_resist),
.jacobian_react_offset = offsetof(ResistorInstance, jacobian_react),
.jacobian_ptr_resist_offset = offsetof(ResistorInstance, jacobian_ptr_resist),
.jacobian_ptr_react_offset = offsetof(ResistorInstance, jacobian_ptr_react),
// TODO add node collapsing to example
// node collapsing
.num_collapsible = 0,
.collapsible = NULL,
.is_collapsible_offset = 0,
// noise
.noise_sources = NULL,
.num_noise_src = 0,
// parameters and op vars
.num_params = NUM_PARAMS,
.num_instance_params = 0,
.num_opvars = 0,
.param_opvar = (OsdiParamOpvar *)&params,
// setup
.access = &osdi_access,
.setup_model = &setup_model,
.setup_instance = &setup_instance,
.eval = &eval,
.load_noise = &load_noise,
.load_residual_resist = &load_residual_resist,
.load_residual_react = &load_residual_react,
.load_spice_rhs_dc = &load_spice_rhs_dc,
.load_spice_rhs_tran = &load_spice_rhs_tran,
.load_jacobian_resist = &load_jacobian_resist,
.load_jacobian_react = &load_jacobian_react,
.load_jacobian_tran = &load_jacobian_tran,
}};

View File

@ -0,0 +1,180 @@
""" test OSDI simulation of resistor
"""
import os, shutil
import numpy as np
import subprocess
import pandas as pd
import sys
sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
from testing import prepare_test
# This test runs a DC, AC and Transient Simulation of a simple resistor.
# The capacitor is available as a C file and needs to be compiled to a shared object
# and then bet put into /usr/local/share/ngspice/osdi:
#
# > make osdi_resistor
# > cp resistor_osdi.so /usr/local/share/ngspice/osdi/resistor_osdi.so
#
# The integration test proves the functioning of the OSDI interface.
# Future tests will target Verilog-A models like HICUM/L2 that should yield exactly the same results as the Ngspice implementation.
directory = os.path.dirname(__file__)
def test_ngspice():
dir_osdi, dir_built_in = prepare_test(directory)
# read DC simulation results
dc_data_osdi = pd.read_csv(os.path.join(dir_osdi, "dc_sim.ngspice"), sep="\\s+")
dc_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "dc_sim.ngspice"), sep="\\s+"
)
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
# read AC simulation results
ac_data_osdi = pd.read_csv(os.path.join(dir_osdi, "ac_sim.ngspice"), sep="\\s+")
ac_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "ac_sim.ngspice"), sep="\\s+"
)
# read TR simulation results
tr_data_osdi = pd.read_csv(os.path.join(dir_osdi, "tr_sim.ngspice"), sep="\\s+")
tr_data_built_in = pd.read_csv(
os.path.join(dir_built_in, "tr_sim.ngspice"), sep="\\s+"
)
# test simulation results
id_osdi = dc_data_osdi["i(vsense)"].to_numpy()
id_built_in = dc_data_built_in["i(vsense)"].to_numpy()
np.testing.assert_allclose(id_osdi[0:20], id_built_in[0:20], rtol=0.01)
return (
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
)
if __name__ == "__main__":
(
dc_data_osdi,
dc_data_built_in,
ac_data_osdi,
ac_data_built_in,
tr_data_osdi,
tr_data_built_in,
) = test_ngspice()
import matplotlib.pyplot as plt
# DC Plot
pd_built_in = dc_data_built_in["v(d)"] * dc_data_built_in["i(vsense)"]
pd_osdi = dc_data_osdi["v(d)"] * dc_data_osdi["i(vsense)"]
fig, ax1 = plt.subplots()
ax1.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
ax1.plot(
dc_data_built_in["v(d)"],
dc_data_built_in["v(d)"] / 10 * 1e3,
label="analytical",
linestyle="--",
marker="s",
)
ax1.plot(
dc_data_osdi["v(d)"],
dc_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
ax1.set_ylabel(r"$I_{\mathrm{P}} (\mathrm{mA})$")
ax1.set_xlabel(r"$V_{\mathrm{PM}}(\mathrm{V})$")
plt.legend()
# AC Plot
omega = 2 * np.pi * ac_data_osdi["frequency"]
z_analytical = 1 / 10
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_built_in["frequency"],
np.ones_like(ac_data_built_in["frequency"]) * z_analytical * 1e3,
label="analytical",
linestyle="--",
marker="s",
)
plt.semilogx(
ac_data_osdi["frequency"], ac_data_osdi["i(vsense)"] * 1e3, label="OSDI"
)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Re \\left\{ Y_{11} \\right\} (\\mathrm{mS})$")
plt.legend()
fig = plt.figure()
plt.semilogx(
ac_data_built_in["frequency"],
ac_data_built_in["i(vsense).1"] * 1e12 / omega,
label="built-in",
linestyle=" ",
marker="x",
)
plt.semilogx(
ac_data_built_in["frequency"],
np.zeros_like(ac_data_built_in["i(vsense).1"]) * 1e12 / omega,
label="analytical",
linestyle="--",
marker="s",
)
plt.semilogx(
ac_data_osdi["frequency"],
ac_data_osdi["i(vsense).1"] * 1e12 / omega,
label="OSDI",
)
plt.ylim(-1, 1)
plt.xlabel("$f(\\mathrm{H})$")
plt.ylabel("$\\Im\\left\{Y_{11}\\right\}/(\\omega) (\\mathrm{pF})$")
plt.legend()
# TR plot
fig = plt.figure()
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["i(vsense)"] * 1e3,
label="built-in",
linestyle=" ",
marker="x",
)
plt.plot(
tr_data_built_in["time"] * 1e9,
tr_data_built_in["v(d)"] / 10 * 1e3,
label="analytical",
linestyle="--",
marker="s",
)
plt.plot(
tr_data_osdi["time"] * 1e9,
tr_data_osdi["i(vsense)"] * 1e3,
label="OSDI",
)
plt.xlabel(r"$t(\mathrm{nS})$")
plt.ylabel(r"$I_{\mathrm{D}}(\mathrm{mA})$")
plt.legend()
plt.show()

586
test_cases/testing.py Normal file
View File

@ -0,0 +1,586 @@
#this file defines some common routines used by the OSDI test cases
import os
import shutil
import glob
from pathlib import Path
from typing import Optional, List, Dict, Tuple
import regex as re
from subprocess import run, PIPE
import pandas as pd
import numpy as np
from math import atan2
import sys
# specify location of Ngspice executable to be tested
directory_testing = os.path.dirname(__file__)
ngspice_path = os.path.join(directory_testing, "../release/src/ngspice")
ngspice_path = os.path.abspath(ngspice_path)
rtol = 0.032
atol_dc = 1e-14
atol_ac = 4e-19
twoPi = 8.0*atan2(1.0,1.0)
def create_shared_objects(directory):
c_files = []
for c_file in glob.glob(directory + "/*.c"):
basename = Path(c_file).stem
c_files.append(basename)
for c_file in c_files:
run(
[
"gcc",
"-c",
"-Wall",
"-I",
"../../src/osdi/",
"-fpic",
c_file + ".c",
"-ggdb",
],
cwd=directory,
)
run(
["gcc", "-shared", "-o", c_file + ".osdi", c_file + ".o", "-ggdb"],
cwd=directory,
)
run(
["mv", c_file + ".osdi", "test_osdi/" + c_file + ".osdi"], cwd=directory
)
run(["rm", c_file + ".o"], cwd=directory)
# for va_file in glob.glob(directory + "/*.va"):
# result = run(
# [
# "openvaf","-b", va_file
# ],
# # capture_output=True,
# cwd=directory,
# )
# run(
# ["cp", result.stdout[:-1], "test_osdi/" + Path(va_file).stem + ".osdi"], cwd=directory
# )
def prepare_dirs(directory):
# directories for test cases
dir_osdi = os.path.join(directory, "test_osdi")
dir_built_in = os.path.join(directory, "test_built_in")
for directory_i in [dir_osdi, dir_built_in]:
# remove old results
shutil.rmtree(directory_i, ignore_errors=True)
# make new directories
os.makedirs(directory_i, exist_ok=True)
return dir_osdi, dir_built_in
def prepare_netlists(directory):
path_netlist = os.path.join(directory, "netlist.sp")
# directories for test cases
dir_osdi = os.path.join(directory, "test_osdi")
dir_built_in = os.path.join(directory, "test_built_in")
# open netlist and activate Ngspice devices
with open(path_netlist) as netlist_handle:
netlist_raw = netlist_handle.read()
netlist_osdi = netlist_raw.replace("*OSDI_ACTIVATE*", "")
netlist_built_in = netlist_raw.replace("*BUILT_IN_ACTIVATE*", "")
# write netlists
with open(os.path.join(dir_osdi, "netlist.sp"), "w") as netlist_handle:
netlist_handle.write(netlist_osdi)
with open(os.path.join(dir_built_in, "netlist.sp"), "w") as netlist_handle:
netlist_handle.write(netlist_built_in)
def run_simulations(dirs):
for dir_i in dirs:
run(
[
ngspice_path,
"netlist.sp",
"-b",
],
cwd=dir_i,
)
def prepare_test(directory):
dir_osdi, dir_built_in = prepare_dirs(directory)
create_shared_objects(directory)
prepare_netlists(directory)
run_simulations([dir_osdi, dir_built_in])
return dir_osdi, dir_built_in
def parse_list(line):
return (val for val in re.split(r"\s+", line) if val != '')
def parse_temps(line):
return [temp for temp in parse_list(line)]
class TestInfo:
biases: Optional[Dict[str, str]] = None
bias_list: Optional[Tuple[str, List[str]]] = None
bias_sweep = None
temps: Optional[List[str]] = None
freqs: Optional[str] = None
dc_outputs: Optional[List[Tuple[str, str]]] = None
ac_outputs: Optional[Dict[str,List[Tuple[str, str, bool, str, str]]]] = None
instanceParameters: str= ""
modelParameters: str = ""
line: str = ""
def __init__(self, name, lines, parent):
self.name = name
self.lines= lines
self.parse()
if self.temps is None:
self.temps = parent.temps
self.pins = parent.pins
self.floating = parent.floating
def parse_temps(self):
temps = parse_temps(self.line)
if self.temps is None:
self.temps = temps
else:
self.temps += temps
def parse_model_params(self):
for param in parse_list(self.line):
path = Path(param)
if path.exists():
self.modelParameters = path.read_text()
else:
self.modelParameters += f"+ {param}\n"
def parse_instance_params(self):
for param in parse_list(self.line):
self.instanceParameters += f" {param}"
def parse_bias_list(self):
if self.bias_list:
raise ValueError(f"ERROR second bias_list spec {self.line}")
res = re.match(r"V\s*\(\s*(\w+)\s*\)\s*=", self.line)
pin = res[1]
vals = self.line[res.end():].strip()
vals = [val for val in re.split(r"\s*,\s*", vals)]
self.bias_list = (pin, vals)
def parse_biases(self):
if self.biases:
raise ValueError(f"ERROR second biases spec {self.line}")
self.biases = {}
for bias in parse_list(self.line):
res = re.match(r"V\s*\(\s*(\w+)\s*\)\s*=", bias)
pin = res[1]
val = bias[res.end():].strip()
self.biases[pin] = val
def parse_outputs(self):
for output in parse_list(self.line):
res = re.match(r"([IV])\s*\(\s*(\w+)\s*\)", output)
if res:
pin = res[2]
if res[1] == "I":
output = f"i(v{pin})", f"I({pin})"
else:
output = f"v({pin})", f"V({pin})"
if self.dc_outputs:
self.dc_outputs.append(output)
else:
self.dc_outputs = [output]
continue
res = re.match(r"([CG])\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)", output)
if res:
kind = res[1]
pin1 = res[2]
pin2 = res[3]
if kind == "G":
output = f"real(i(v{pin1}))", f"g({pin1},{pin2})", False, pin1, pin2
elif kind == "C":
output = f"imag(i(v{pin1}))", f"c({pin1},{pin2})", True, pin1, pin2
if self.ac_outputs:
if pin2 in self.ac_outputs:
self.ac_outputs[pin2].append(output)
else:
self.ac_outputs[pin2] = [output]
else:
self.ac_outputs = {pin2: [output]}
continue
def parse_frequency(self):
res = re.match(r"(lin|oct|dec)\s+(\S+)\s+(\S+)\s+(\S+)\s*", self.line)
kind = res[1]
num_steps = int(res[2])
start = res[3]
end = res[4]
if start != end:
if kind == "lin":
num_points = num_steps + 1
else:
num_points = num_steps
else:
assert num_steps == 1
num_points = 1
self.freqs = f"{kind} {num_points} {start} {end}"
def parse_bias_sweep(self):
res = re.match(r"V\s*\(\s*(\w+)\s*\)\s*=", self.line)
pin = res[1]
args = self.line[res.end():]
args = [float(arg) for arg in re.split(r"\s*,\s*", args)]
if len(args) != 3:
raise ValueError(f"bias sweep must have 3 arguments found {args} in {self.line}")
self.bias_sweep = (pin, args)
def try_parse(self, prefix: str, f):
if self.line.startswith(prefix):
self.line = self.line[len(prefix):].strip()
f()
def parse_line(self):
if self.try_parse("temperature", self.parse_temps):
return
if self.try_parse("modelParameters", self.parse_model_params):
return
if self.try_parse("instanceParameters", self.parse_instance_params):
return
if self.try_parse("biasList", self.parse_bias_list):
return
if self.try_parse("listBias", self.parse_bias_list):
return
if self.try_parse("biases", self.parse_biases):
return
if self.try_parse("output", self.parse_outputs):
return
if self.try_parse("outputs", self.parse_outputs):
return
if self.try_parse("biasSweep", self.parse_bias_sweep):
return
if self.try_parse("freq", self.parse_frequency):
return
if self.try_parse("frequency", self.parse_frequency):
return
def parse(self):
for line in self.lines:
self.line = line
self.parse_line()
def gen_netlist(self, osdi_file, va_module, type_arg):
if self.bias_list:
bias_start = f"foreach bias {' '.join(self.bias_list[1])}\nalter v{self.bias_list[0]}=$bias"
bias_end = "end"
else:
bias_start = bias_end = ""
if self.dc_outputs:
if not self.bias_sweep:
raise ValueError("dc bias sweep msising")
outputs = " ".join(output for output, _ in self.dc_outputs)
sweep = f"dc v{self.bias_sweep[0]} {self.bias_sweep[1][0]} {self.bias_sweep[1][1]} {self.bias_sweep[1][2]}\n wrdata {self.dc_results_path()} {outputs}"
elif self.ac_outputs:
freqs = self.freqs
if not self.freqs:
freqs = f"lin 1 {1/twoPi} {1/twoPi}"
if self.bias_sweep:
if self.bias_list:
bias_start += "\n"
bias_end += "\n"
vals = np.arange(self.bias_sweep[1][0], self.bias_sweep[1][1] + self.bias_sweep[1][2]*0.1, self.bias_sweep[1][2])
vals = [str(val) for val in vals]
bias_start += f"foreach bias {' '.join(vals)}\nalter v{self.bias_sweep[0]}=$bias"
bias_end += "end"
sweep = ""
for pin, outputs in self.ac_outputs.items():
sweep += f"alter v{pin} ac = 1\nac {freqs}\n"
outputs = " ".join(output[0] for output in outputs)
sweep += f"wrdata {self.ac_results_path(pin)} {outputs}\n"
sweep += f"alter v{pin} ac = 0\n"
else:
return ""
biases = self.biases
if not biases:
biases = dict()
source = "\n".join(f"v{pin} {pin} {0} dc={biases.get(pin, 0)}" for pin in self.pins if not pin in self.floating)
source += "".join(f"\nr{i} {pin} {0} r=1G" for i,pin in enumerate(self.floating))
return f"""CMC testsuite {self.name}
.options abstol=1e-15
{source}
.model test_model {va_module}
{self.modelParameters} {type_arg}
A1 {' '.join(self.pins)} test_model {self.instanceParameters}
.control
pre_osdi {osdi_file}
set filetype=ascii
set wr_vecnames
set wr_singlescale
set appendwrite
foreach tamb {' '.join(self.temps)}
set temp=$tamb
{bias_start}
{sweep}
{bias_end}
end
quit 0
.endc
.end
"""
def dc_results_path(self, old=False) -> Path:
dir = "results"
if old:
dir = "results_old"
return Path(dir)/f"{self.name}.ngspice"
def ac_results_path(self, pin: str, old=False) -> Path:
dir = "results"
if old:
dir = "results_old"
return Path(dir)/f"{self.name}_{pin}.ngspice"
def run(self, osdi_file, va_module, type_arg, old_sim_ref=False, capture=True, check=True):
if not (self.dc_outputs or self.ac_outputs):
return
print(f"running {self.name}...")
netlist_path = Path("netlists")/f"{self.name}.sp"
netlist = self.gen_netlist(osdi_file, va_module, type_arg)
Path(netlist_path).write_text(netlist)
res = run([ngspice_path, netlist_path, "-b"], capture_output=capture)
res.check_returncode()
# res.check_returncode()
reference_path = Path("reference")/f"{self.name}.standard"
references = pd.read_csv(reference_path, sep="\\s+")
if not check:
return
if self.dc_outputs:
results_path = self.dc_results_path()
if not results_path.exists():
print(f"ERROR check failed for {self.name}\nsimulation file is missing - likely convergence issues!")
return
results = pd.read_csv(results_path, sep="\\s+")
results = results.apply(pd.to_numeric, errors='coerce')
firstcol = results.iloc[:,1].to_numpy()
results = results[np.bitwise_not(np.isnan(firstcol))]
if old_sim_ref:
ref_path = self.dc_results_path(old=True)
references = pd.read_csv(ref_path, sep="\\s+")
references = references.apply(pd.to_numeric, errors='coerce')
firstcol = references.iloc[:,1].to_numpy()
references = references[np.bitwise_not(np.isnan(firstcol))]
for result_col, ref_col in self.dc_outputs:
reference = references[ref_col].to_numpy()
result = results[result_col].to_numpy()
if "I(" in ref_col:
result = -result
adiff = np.abs(result-reference)
rdiff = adiff/np.abs(reference)
err = np.bitwise_not(np.bitwise_or(rdiff < rtol, adiff < atol_dc))
if not np.any(err):
continue
maxatol = np.max(adiff[err])
maxrtol = np.max(rdiff[err])
print(f"ERROR check failed for {ref_col}\nrtol={maxrtol} atol={maxatol}\nresult:\n{result[err]}\nreference:\n{reference[err]}\nrtol:\n{rdiff[err]}")
elif self.ac_outputs:
for pin, outputs in self.ac_outputs.items():
results_path = self.ac_results_path(pin)
if not results_path.exists():
print(f"ERROR check failed for {self.name} (ac {pin})\nsimulation file is missing - likely convergence issues!")
continue
results = pd.read_csv(results_path, sep="\\s+")
results = results.apply(pd.to_numeric, errors='coerce')
firstcol = results.iloc[:,1].to_numpy()
results = results[np.bitwise_not(np.isnan(firstcol))]
if old_sim_ref:
ref_path = self.ac_results_path(pin, old=True)
references = pd.read_csv(ref_path, sep="\\s+")
references = references.apply(pd.to_numeric, errors='coerce')
firstcol = references.iloc[:,1].to_numpy()
references = references[np.bitwise_not(np.isnan(firstcol))]
for result_col, ref_col, is_cap, pin1, pin2 in outputs:
result = results[result_col].to_numpy()
if old_sim_ref:
reference = references[result_col].to_numpy()
# print(ref_col)
# print(references)
# print(results)
else:
reference = references[ref_col].to_numpy()
if not old_sim_ref:
if is_cap:
if"Freq" in references:
result = result /(twoPi*results["frequency"])
if pin1 == pin2:
result = -result
else:
result = -result
adiff = np.abs(result-reference)
rdiff = adiff/np.abs(reference)
err = np.bitwise_not(np.bitwise_or(rdiff < rtol, adiff < atol_ac))
if not np.any(err):
continue
maxatol = np.max(adiff[err])
maxrtol = np.max(rdiff[err])
print(f"ERROR check failed for {ref_col}\nrtol={maxrtol} atol={maxatol}\nresult:\n{result[err]}\nreference:\n{reference[err]}\nrtol:\n{rdiff[err]}")
def removeComments(string):
string = re.sub(re.compile(r"/\*.*?\*/",re.DOTALL ) ,"" ,string) # remove all occurrences streamed comments (/*COMMENT */) from string
string = re.sub(re.compile(r"//.*?\n" ) ,"" ,string) # remove all occurrence single-line comments (//COMMENT\n ) from string
return string
class QaSpec:
temps: List[str]
pins: List[str]
floating: List[str]
tests: List[TestInfo]
dir: Path
def __init__(self, dir: Path):
self.dir = dir
self.temps = []
self.pins = []
self.tests = []
self.floating = []
self.parse()
def parse(self):
old_dir = os.getcwd()
os.chdir(self.dir)
qa_spec = Path("qaSpec").read_text()
qa_spec = removeComments(qa_spec)
lines = [line.strip() for line in qa_spec.split('\n')]
i = 0
while i < len(lines):
line = lines[i]
i+= 1
if line.startswith("temperature"):
line = line[len("temperature"):]
self.temps = parse_temps(line)
elif line.startswith("pins"):
line = line[len("pins"):]
self.pins = [pin for pin in re.findall(r"\w+", line) if pin != "pins"]
elif line.startswith("float") or line.startswith("floating"):
self.floating = [pin for pin in re.findall(r"\w+", line) if pin != "floating" and pin != "float"]
elif line.startswith("test"):
test_name = line[4:].strip()
start = i
while i < len(lines) and lines[i] != "":
i += 1
end = i
test = TestInfo(test_name, lines[start:end], self)
self.tests.append(test)
os.chdir(old_dir)
def run(self, va_file, va_module, type_arg, filter=None, openvaf=None, cache = None, old_sim_ref=False, capture=True, check=True):
if openvaf:
if not cache:
result = run(
["md5sum", openvaf],
stdout=PIPE,
)
result.check_returncode()
md5sum = result.stdout[:-1].decode("utf-8").split(" ")[0]
cache = f"./.cache/{md5sum}"
Path(cache).mkdir(parents=True,exist_ok=True)
else:
openvaf = "openvaf"
args = [openvaf,"-b", va_file]
if cache:
args.append("--cache-dir")
args.append(cache)
# print(args, cache)
result = run(
args,
stdout=PIPE,
)
result.check_returncode()
osdi_file = result.stdout[:-1].decode("utf-8")
old_dir = os.getcwd()
os.chdir(self.dir)
dirpath = Path('netlists')
if dirpath.exists():
shutil.rmtree(dirpath)
os.mkdir("netlists")
dirpath = Path('results')
if old_sim_ref:
old_path = Path("results_old")
if old_path.exists():
shutil.rmtree(old_path)
shutil.move(dirpath,old_path)
elif dirpath.exists():
shutil.rmtree(dirpath)
dirpath.mkdir(exist_ok=False)
for test in self.tests:
if filter and not test.name in filter:
continue
test.run(osdi_file, va_module, type_arg, old_sim_ref=old_sim_ref, capture=capture, check=check)
os.chdir(old_dir)

View File

View File

7
test_docker.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
IMAGENAME="registry.gitlab.com/dospm/ngspice"
TAG="latest"
docker build -t $IMAGENAME:$TAG .
docker run -it --rm --user "$(id -u)":"$(id -g)" -v "${PWD}":/tmp $IMAGENAME:$TAG /bin/bash