What is a Derivative?
The python_derivative_v1_1.mdl IC-CAP model file attached at the bottom of this post includes several transforms to compute the derivative:
Using Python Numpy functions - gradient() and diff()
Using the PEL (Programming Extraction Language) derivative function explored in a transform named Test_PEL
I will illustrate the use of IC-CAP's built-in derivative functions in PEL as follows.
########################################################################################
der = derivative2(x,y,1,34)
print der
complex gm.M.11[size(der)]
i=0
while i < 34
gm.M.11[i] = der[i]
i = i + 1
end while
return gm
I wanted to implement the same derivative calculation using the IC-CAP Python environment. I've developed a new library for implementing some numerical methods including two different derivative functions that can be called from our transforms. This ic_math.py file is included as an attachment to this article. Download the file and copy it to your ICCAP_USER_PYTHON_PATH (typically "C:\Users\<username>\iccap\python").
In my first attempt, I used the diff function from the Numpy library as shown in the function icm_derivative_npdiff code listing from ic_math.py:
Now that we have the function defined in our library we can import it from our Test_npdiff transform and call the function to compute the derivative of our sample I-V data set.
# Test_npdiff.py
# Computes the derivative of I-V data using Numpy Diff function
#############################################################################################
from iccap import set_return_array
import numpy as np
import ic_math as m
dydx = m.icm_derivative_npdiff(x,y)
if debug: print dydx
gm = [ dydx[i] for i in range(0,len(x)-1) ]
if debug: print "initial gm = {}".format(gm)
# insert dydx starting element to gm at index = 0
# dydx array has 1 less value due to diff function return
gm.insert(0,1E-10)
if debug: print "gm = {}".format(gm)
set_return_array("M", gm)
In my experience, this is usually adequate and the error is negligible when performing extractions of threshold voltage Vth from the Id vs. Vg characteristics of a MOSFET transistor. During this calculation, we compute the derivative of Id with respect to Vg and then find the maximum of the derivative (i.e. Gm_max). At that interpolated Vg, we define a linear function whose slope is equal to Gm_max to superimpose on the IdVg curves. This is illustrated in Ma Long's blog post entitled "Device Modeling 101 - How to Extract Threshold Voltage of MOSFETs." The error in performing these steps is usually greater than any round-off error that may have occurred in computing the derivative. In other words, the simple forward difference approximation for the derivative is usually close enough for this application.
After running the Test_npdiff transform and comparing the results to the built-in IC-CAP PEL function, I noticed that there was a discrepancy in the computed derivatives, especially in the first points of the I-V curve. The numpy.diff() function was slightly under-estimating the derivative even-though the overall slope of the line was pretty close. The maximum error was at the lower boundary of the curve. Furthermore, the vector returned was one point less in length. When plotting the derivative result against the original I-V data, I had to pad the first point to realign the data points.
Was there an error in my math? Looking at the PEL implementation of the derivative, I noticed that it was using a central difference approximation method which is often more accurate. The simple algorithm shown above using numpy.diff() uses the forward difference approximation method, which can be subject to round-off errors when the step size is small.
After some more investigation into the Numpy library functions, I discovered a function that will produce the same result of the Test_PEL transform - the numpy.gradient() function. This function uses the second order accurate central difference method for most of the vector. Furthermore, the vector length is preserved, by gracefully handling the lower and upper boundaries. To evaluate this method, I created an additional Python function in the ic_math library called icm_derivative_npgrad to implement the gradient method as shown below in the code listing:
Now we can call the new icm_derivative_npgrad function from our transform named Test_npgrad which will uses the gradient function.
#############################################################################################
# Test_npgrad.py
# Computes the derivative of I-V data using Numpy Gradient function
#############################################################################################
from iccap import set_return_array
import numpy as np
import ic_math as m
dydx = m.icm_derivative_npgrad(x,y,1,2)
if debug: print dydx
gm = [ dydx[i] for i in range(0,len(x)-1) ]
if debug: print "initial gm = {}".format(gm)
set_return_array("M", gm)
You may notice that I've used a new function set_return_array() added to the IC-CAP 2018 Python API which returns a list from a temporary variable to the Transform dataset. This allows this data to be accessible for plotting and is analogous to the RETURN statement in PEL.
As you can see from the table below, the icm_derivative_npgrad function yields exactly the same results as the PEL derivative2 function. Meanwhile, the numpy.diff function underestimates the derivative. I padded the numpy.diff() generated gm data so the first point is non-zero and of the same magnitude of the other data to allow easily plotting the results above. See that the blue and green traces in the graph above are right on top of each other.