craig@craigjb.com
GitHubhttps://github.com/craigjb Mastodonhttps://twitter.com/craig_jbishop
« Gameslab high-level design Gameslab first steps »

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:

Simple model for simulating signal integrity

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!

Plot of IBIS DDR3 signal integrity simulation

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 :)

« Gameslab high-level design Gameslab first steps »

Copyright © 2017 Craig J Bishop