Basic IBIS simulation with eispice
While working on the DDR3 interface of the re-imagined Gameslab, I wanted to do some simple sanity checks for signal integrity. Since I don’t really want to pay >$1000 for two PCBs with controlled impedance and a custom stackup, I just wanted to verify my cheap boards from PCB-POOL would work at all. PCB POOL lists the stackup for 6-layer boards on their site, so I plugged these specs into a trace impedance calculator. My best option so far looks like routing the high-speed DDR traces on the inner layers with a reference ground plane above each signal layer. I calculated the impedance at ~63 Ω. Since DDR3 has 34 Ω driver and typically 40 Ω transmission lines, I wanted to at least do a sanity check simulation.
Sadly, there are not many free or open-source options for doing SI simulations. The one I found, eispice supports IBIS models, but it took a bit of work to get the simulation working. The IBIS support isn’t comprehensive, and it seems eispice only supports simulating a rising waveform or a falling waveform, no repetition. Anyway, here’s the simple circuit I wanted to simulate:
This represents one of the address/command/control pins on the Zynq series terminated and connected to the DDR3 SDRAM through a 63 Ω impedance trace. I want to see if different series termination resistors have any effect, and I want to see if the rise time is near adequate. The eispice examples page has a circuit that is very close:
# example from http://www.thedigitalmachine.net/eispice.examples.html
import eispice
ibs = eispice.Ibis('test')
cct = eispice.Circuit('IBIS Test')
cct.Driver = ibs['2']('vs')
cct.Rt = eispice.R('vs', 'vi', 33.2)
cct.Tg = eispice.T('vi', 0, 'vo', 0, 50, '2n')
cct.Receiver = ibs['1']('vo')
cct.tran('0.01n', '20n')
eispice.plot(cct)
Now, grab the Xilinx Zynq IBIS models and the Micron DDR3 IBIS model. If you try to import the models as they are, you will get a result like below:
In [1]: import eispice
In [2]: zynq_ibis = eispice.Ibis('zynq7.ibs')
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-2-41cc8ef3ed85> in <module>()
----> 1 zynq_ibis = eispice.Ibis('zynq7.ibs')
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis.pyc in __init__(self, filename, device)
92 """
93
---> 94 Ibis_Parser.__init__(self, filename, device)
95
96 def __getitem__(self, pin):
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in __init__(self, filename, device)
560 self.addRE(_reAny, self.handleUnkown)
561
--> 562 self.process()
563
564 self.fdin.close()
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in process(self)
99 def process(self):
100 for line in iter(self.fdin.readline, ''):
--> 101 if self._process(line) is Done:
102 self.fdin.seek(-len(line),1)
103 break
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in _process(self, line)
92 if match:
93 if callable(handler):
---> 94 return handler(**match.groupdict())
95 else:
96 return handler
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in handleUnkown(self, line)
167
168 def handleUnkown(self, line):
--> 169 raise RuntimeError, 'Unsupported Line\n%s' % line
170
171 # -------------------------------------------------------------------------- #
RuntimeError: Unsupported Line
[r series] 77.8408 97.4321 61.2294
Well, that looks pretty disappointing. However, if you open up the IBIS file and take a look, there’s a ton of irrelevant stuff for this simulation. And, the above tries to import all of it. Maybe if all the rest is stripped out, the problem might look more solvable. So, I opened up the IBIS model in a text editor, and started ripping out sections. First, eispice choked on Windows-style carriage returns, so strip those. I put some notes on the rest inline here:
...
| looks like good stuff to keep
[Component] ZYNQ7
[Manufacturer] Xilinx Inc.
[Package]
...
| WATCH OUT HERE, you need to pick the right section and uncomment it!
| I'm doing a DDR sim, so I uncommented the section for my package under
| the ps_ddr header
|
| Zynq ps_ddr Package Parasitics
|
| Date: 7/30/12 Initial Release
| R_pkg values characterized at DC
|
| If utilizing the package typ, min, max data in your simulations
| uncomment the desired R_pkg, L_pkg and C_pkg lines.
| Be sure to comment out all the unused lines in this section.
|
| Note that the detailed and fully coupled .pkg file is available for
| all these packages, and may be requested and invoked in the
| [package model] section.
|
...
|CLG225
|variable typ min max
R_pkg 347.3m 161.7m 566.2m
L_pkg 6.11nH 2.18nH 10.31nH
C_pkg 0.97pF 0.51pF 1.59pF
| Things get interesting here. This IBIS file supports multiple packages,
| so the pin names don't matter too much. Xilinx docs say to use the
| SSTL15_S_PSDDR, so I just need that on a pin name I can use.
|************************************************************************
[Pin] signal_name model_name
1 SSTL15_S_PSDDR SSTL15_S_PSDDR
...
| Now there are a ton of model sections. Look for the SSTL15_S_PSDDR model
| or whatever one you want to simulate, and strip out everything else
...
|************************************************************************
| Model SSTL15_S_PSDDR
|************************************************************************
|
[Model] SSTL15_S_PSDDR
Model_type I/O
Polarity Non-Inverting
Enable Active-Low
Vinl = 0.65
Vinh = 0.85
Vmeas = 0.7500V
Cref = 0.0F
Rref = 50.0000
Vref = 0.7500V
C_comp 2.70pF 2.69pF 2.70pF
|
[Temperature Range] 25.0000 85.0000 0.0
[Voltage Range] 1.5000V 1.4250V 1.5750V
[Pulldown]
|Voltage I(typ) I(min) I(max)
|
-1.50 -37.5000mA -28.9000mA -43.2200mA
-1.45 -37.5000mA -28.9000mA -43.2200mA
...
2.95 30.0000mA 21.1000mA 37.0000mA
3.00 30.0000mA 21.1000mA 37.0000mA
|
[Pullup]
|Voltage I(typ) I(min) I(max)
|
-1.50 60.4000mA 46.2000mA 69.7200mA
-1.45 60.4000mA 46.2000mA 69.7200mA
...
2.95 -30.7000mA -22.0000mA -41.2000mA
3.00 -30.7000mA -22.0000mA -41.2000mA
|
[GND_clamp]
|Voltage I(typ) I(min) I(max)
|
-1.50 -6.2610A -5.9750A -6.4580A
-1.46 -5.7940A -5.5750A -5.9530A
...
1.46 1.9540nA 35.2100nA 1.7260nA
1.50 2.6280nA 79.8600nA 1.9850nA
|
[POWER_clamp]
|Voltage I(typ) I(min) I(max)
|
-1.50 0.8252A 0.9116A 0.7915A
-1.48 0.7942A 0.8811A 0.7604A
...
-0.02 3.4410nA 28.0400nA 4.6030nA
0.00 2.6280nA 22.4700nA 3.3260nA
|
[Ramp]
| variable typ min max
dV/dt_r 0.5864/0.1837n 0.488/0.2561n 0.6801/0.1475n
dV/dt_f 0.6414/0.1844n 0.4812/0.2714n 0.7194/0.1347n
R_load = 50.0000
|
[Rising Waveform]
R_fixture= 50.0000
V_fixture= 0.7500
V_fixture_min= 0.7125
V_fixture_max= 0.7875
|time V(typ) V(min) V(max)
|
0.00S 201.0000mV 248.6000mV 174.8000mV
30.00pS 201.0000mV 248.6000mV 174.7000mV
...
1.64nS 1.2700V 1.1510V 1.3730V
1.67nS 1.2700V 1.1510V 1.3730V
|
[Rising Waveform]
R_fixture= 50.0000
V_fixture= 0.0
|time V(typ) V(min) V(max)
|
0.00S -1.3070uV -259.5000nV -1.8172uV
30.00pS -1.4919uV -1.0851uV -12.9827uV
...
2.21nS 975.3000mV 781.3000mV 1.1260V
2.24nS 975.3000mV 781.3000mV 1.1260V
|
[Falling Waveform]
R_fixture= 50.0000
V_fixture= 0.7500
V_fixture_min= 0.7125
V_fixture_max= 0.7875
|time V(typ) V(min) V(max)
|
0.00S 1.2700V 1.1510V 1.3730V
30.00pS 1.2700V 1.1510V 1.3730V
...
1.85nS 201.0000mV 248.7000mV 174.8000mV
1.88nS 201.0000mV 248.7000mV 174.8000mV
|
[Falling Waveform]
R_fixture= 50.0000
V_fixture= 0.0
|time V(typ) V(min) V(max)
|
0.00S 975.3000mV 781.3000mV 1.1260V
30.00pS 975.3000mV 781.3000mV 1.1260V
...
1.46nS 42.4740uV 66.2950uV 35.2830uV
1.49nS 40.9790uV 60.0000uV 33.8900uV
|
|End [Model] SSTL15_S_PSDDR
|End [Component]
|
...
|
|*************************************************************************
[END]
|
And now if we try it, this time instantiating a driver device from a pin model:
In [1]: import eispice
In [2]: zynq_ibis = eispice.Ibis('zynq7.ibs')
In [3]: cct = eispice.Circuit('DDR3 sim')
# here '1' is the pin name from above
# and 'vs' is the driver output node name (like a SPICE node)
In [4]: cct.Driver = zynq_ibis['1']('vs')
No error! Now, if I try the Micron IBIS model, well it breaks too, but with a different error:
In [1]: import eispice
In [2]: micron_ibis = eispice.Ibis('v89c.ibs')
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-2-32991fd5d268> in <module>()
----> 1 micron_ibis = eispice.Ibis('v89c.ibs')
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis.pyc in __init__(self, filename, device)
92 """
93
---> 94 Ibis_Parser.__init__(self, filename, device)
95
96 def __getitem__(self, pin):
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in __init__(self, filename, device)
560 self.addRE(_reAny, self.handleUnkown)
561
--> 562 self.process()
563
564 self.fdin.close()
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in process(self)
99 def process(self):
100 for line in iter(self.fdin.readline, ''):
--> 101 if self._process(line) is Done:
102 self.fdin.seek(-len(line),1)
103 break
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in _process(self, line)
92 if match:
93 if callable(handler):
---> 94 return handler(**match.groupdict())
95 else:
96 return handler
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_parser.pyc in handleUnkown(self, line)
167
168 def handleUnkown(self, line):
--> 169 raise RuntimeError, 'Unsupported Line\n%s' % line
170
171 # -------------------------------------------------------------------------- #
RuntimeError: Unsupported Line
[package model] v89c_78ball_pkg
Well, might as well strip out all the unnecessary stuff here too:
...
|
[IBIS Ver] 4.2
[File Name] v89c.ibs
[Date] 02/03/2012
[File Rev] 2.0
[Source] From silicon level SPICE model at Micron Technology, Inc.
For support e-mail modelsupport@micron.com
|
[Copyright] Copyright 2012 Micron Technology, Inc. All rights reserved.
...
| WATCH OUT, there are multiple component definitions in here
| strip out the other ones and leave only the component of interest
|***************************************************************************
| COMPONENT: MT41J128M16JT (96-Ball FBGA, x16, 8mm X 14mm)
|***************************************************************************
|
[Component] MT41J128M16JT
| WATCH OUT, eispice chokes on this line below, so comment out
|[Package Model] v89c_96ball_pkg
[Manufacturer] Micron Technology,Inc.
[Package]
| typ min max
R_pkg 345.04m 192.55m 567.10m
L_pkg 1.78nH 0.861nH 3.365nH
C_pkg 0.36pF 0.254pF 0.524pF
|
...
| I decided to simulate the A0 pin, arbitrarily
| WATCH OUT, eispice turns all pin names to lower case for some reason
| and it IS case sensitive!
| AND, change "INPUT2" to the full model name "INPUT2_1600"
| so we don't have to use model selector stuff
[Pin] signal_name model_name R_pin L_pin C_pin
N3 A0 INPUT2_1600 324.54m 1.598nH 0.353pF
...
| Grab just the relevant model (by speed grade too, e.g. the "_1600" part)
| it looks a little different in this IBIS file, but we'll see
|
|***************************************************************************
| MODEL INPUT2_1600 (Add/Cmd/Ctrl Input Model, 1333/1600Mbps)
|***************************************************************************
|
[Model] INPUT2_1600
Model_type Input
|
Vinl = 600.000mV
Vinh = 900.000mV
|
| typ min max
|
C_comp 0.660pF 0.585pF 0.735pF
|
[Model Spec]
| Input threshold voltage corners
Vinl 0.6000V 0.5625V 0.6375V
Vinh 0.9000V 0.8625V 0.9375V
|
| Dynamic Overshoot Parameters from DDR3 Specification
|D_overshoot_ampl_h 0.40 NA NA
|D_overshoot_ampl_l 0.40 NA NA
|D_overshoot_area_h 0.33n NA NA
|D_overshoot_area_l 0.33n NA NA
|
| WATCH OUT, eispice chokes on this section below, comment out
|[Receiver Thresholds]
|Vth = 0.750V
|Vth_min = 0.7350V
|Vth_max = 0.7650V
|Vinh_ac = 0.150V
|Vinh_dc = 0.100V
|Vinl_ac = -0.150V
|Vinl_dc = -0.100V
|Tslew_ac = 5.000ns |Not specified, so set to high value
|Threshold_sensitivity = 0.50
|Reference_supply Power_clamp_ref
|
[Voltage Range] 1.5000V 1.4250V 1.5750V
[POWER Clamp Reference] 1.5000V 1.4250V 1.5750V
| Junction Temperature (Ambient temp is 35C typ, 95C min, 0C max)
[Temperature Range] 50.0 110.0 0.0
|
|***************************************************************************
|
[GND Clamp]
|
| Voltage I(typ) I(min) I(max)
|
-1.50000000E+0 -131.93755000E-3 -106.31704000E-3 -152.40004000E-3
-1.49500000E+0 -130.41340000E-3 -104.87407000E-3 -150.81963000E-3
...
-290.00000000E-3 NA NA 0.00000000E+0
1.57500000E+0 0.00000000E+0 0.00000000E+0 0.00000000E+0
3.00000000E+0 0.00000000E+0 0.00000000E+0 0.00000000E+0
|
[POWER Clamp]
|
| Voltage I(typ) I(min) I(max)
|
-1.50000000E+0 94.89989300E-3 90.42406100E-3 97.73690400E-3
-1.49500000E+0 94.15744300E-3 89.69524700E-3 96.98225500E-3
...
-290.00000000E-3 0.00000000E+0 NA NA
1.57500000E+0 0.00000000E+0 0.00000000E+0 0.00000000E+0
3.00000000E+0 0.00000000E+0 0.00000000E+0 0.00000000E+0
|
| There's some extra stuff here like sparse package models etc. but I had
| trouble getting it to work
[End]
Cool, now it seems to import fine. Let’s run the simulation:
In [1]: import eispice
In [2]: zynq_ibis = eispice.Ibis('zynq7.ibs')
In [3]: micron_ibis = eispice.Ibis('v89c.ibs')
In [4]: cct = eispice.Circuit('IBIS DDR3 Sim')
In [5]: cct.Driver = zynq_ibis['1']('vs')
# note the lower-case 'n3'
In [6]: cct.Receiver = micron_ibis['n3']('vo')
Well that blew up too:
In [6]: cct.Receiver = micron_ibis['n3']('vo')
ERROR:simulator ./module/simulatormodule.c:79 r->pw == NULL --
ERROR:simulator ./module/simulatormodule.c:99 pwInit(r, args, kwds) --
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-13833dc28005> in <module>()
----> 1 cct.Receiver = micron_ibis['n3']('vo')
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis.pyc in __call__(self, node, speed, direction, io, modelName)
103 def __call__(self, node, speed=Typical, direction=Rising, io=Output,
104 modelName=None):
--> 105 return Pin(self.ibs, self.pin, node, speed, direction, io, modelName)
106
107 return PinBuilder(pin.lower(), self)
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_device.pyc in __init__(self, ibs, pinName, node, speed, direction, io, modelName)
221 ((model.model_type == 'i/o') and io==Input)):
222
--> 223 self.Buffer = Receiver(die, vcc, 0, model, speed)
224
225 elif ((model.model_type == 'output') or
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/ibis_device.pyc in __init__(self, iNode, pwrNode, gndNode, model, speed)
56 if hasattr(model, 'gnd_clamp'):
57 self.VI_gnd = device.VI(iNode, gndNode,
---> 58 waveform.PWL(model.gnd_clamp[speed]))
59 if hasattr(model, 'power_clamp'):
60 self.VI_pwr = device.VI(pwrNode, iNode,
/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/eispice/waveform.pyc in __init__(self, data)
68 data = units.floatList2D(data)
69 data = data[data[:,0].argsort(),] # sort by first column for simulator
---> 70 simulator_.PWL_.__init__(self, data)
71
72 class PWC(simulator_.PWC_):
TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'
After banging my head for awhile, I found out this is related to certain fields in the IBIS having “NA” values. I went back and changed them to 0.0000, fitting in with the values on either side.
| example of an NA value
| I just changed them to the 0 value
| -290.00000000E-3 0.00000000E+0 NA NA
| so:
-290.00000000E-3 0.00000000E+0 0.00000000E+0 0.00000000E+0
Now, running the simulation:
In [1]: import eispice
In [2]: zynq_ibis = eispice.Ibis('zynq7.ibs')
In [3]: micron_ibis = eispice.Ibis('v89c.ibs')
In [4]: cct = eispice.Circuit('IBIS DDR3 Sim')
In [5]: cct.Driver = zynq_ibis['1']('vs')
In [6]: cct.Receiver = micron_ibis['n3']('vo')
# 40 ohm series term resistor
In [7]: cct.Rt = eispice.R('vs', 'vi', 40)
# eispice.GND is the return on each side of the transmission line
# 63 is the characteristic impedance, and 260p is the propagation
# for 1.5 inches of this line delay
In [8]: cct.Tline = eispice.T('vi', eispice.GND, 'vo', eispice.GND, 63, '260p')
In [9]: cct.tran('0.01n', '10n')
In [10]: eispice.plot(cct)
And finally, a result!
I wanted to do a few more analyses that eispice doesn’t support very well. Luckily, it’s pretty simple to dump the result data.
# looked in eispice modules/plot.py to figure this out
In [11]: vo_data = cct.results[cct.variables.index('v(vo)')]
In [12]: time = cct.results[0]
# do anything you want with the results here
# matplotlib is nice :)