craig@craigjb.com
Githubhttps://github.com/craigjb Twitterhttps://twitter.com/craig_jbishop

Reversing Away an Annoying Pop-Up

I recently started using an app called Be Focused Pro for focusing using the Pomodoro technique to focus a bit better. The free version of the app has ads and some pop-ups, so I paid $5 for the pro version to remove the ads. Afterward, I was unhappy to discover the app still persisted in annoying its pro users. Periodically, when you enter a 5-minute break, the app triggers a pop-up notification asking to rate the app in the AppStore. While normally I would just find another app, I actually really like this one’s user interface (and I already paid $5 for it). So instead, I decided to hack away the annoying pop-up and have the app I want.

Annoying pop-up dialog that appears periodically

First, I opened the app binary in Hopper Disassembler, which is an amazingly priced alternative to IDA Pro for static analysis on Mac. To find the code that opens the stupid pop-up, we can search for the pop-up string and follow the references back to the code that actually runs the pop-up. So first, I searched for the string “Please take a moment”, and then clicked to follow the cross-references back to the code.

Finding the pop-up string in the disassembly
Finding the pop-up string in the disassembly

Following the XREFs by double-clicking, I ended up in the middle of an Objective-C method called -[XWRate initialize]:. It looks like this app was built using Objective-C, and it looks like this XWRate class probably handles the annoying pop-up. To get a better look at what’s happening, I ran the binary through class-dump, which outputs the class declarations so we can see what methods are in the XWRate class.

@interface XWRate : NSWindowController
{
    BOOL _previewMode;
    NSTextField *_titleField;
    NSTextView *_descriptionField;
    NSButton *_rateButton;
    NSButton *_remindButton;
    unsigned long long _daysUntilPrompt;
    unsigned long long _remindPeriod;
    NSString *_messageTitle;
    NSString *_message;
    NSString *_rateButtonLabel;
    NSString *_dontLikeButtonLabel;
    NSString *_remindButtonLabel;
    NSString *_appStoreID;
    NSString *_appName;
}

+ (void)rateAppButtonHandler;
+ (id)instance;
@property(retain, nonatomic) NSString *appName; // @synthesize appName=_appName;
@property(retain, nonatomic) NSString *appStoreID; // @synthesize appStoreID=_appStoreID;
@property(nonatomic) BOOL previewMode; // @synthesize previewMode=_previewMode;
@property(retain, nonatomic) NSString *remindButtonLabel; // @synthesize remindButtonLabel=_remindButtonLabel;
@property(retain, nonatomic) NSString *dontLikeButtonLabel; // @synthesize dontLikeButtonLabel=_dontLikeButtonLabel;
@property(retain, nonatomic) NSString *rateButtonLabel; // @synthesize rateButtonLabel=_rateButtonLabel;
@property(retain, nonatomic) NSString *message; // @synthesize message=_message;
@property(retain, nonatomic) NSString *messageTitle; // @synthesize messageTitle=_messageTitle;
@property(nonatomic) unsigned long long remindPeriod; // @synthesize remindPeriod=_remindPeriod;
@property(nonatomic) unsigned long long daysUntilPrompt; // @synthesize daysUntilPrompt=_daysUntilPrompt;
@property(retain, nonatomic) NSButton *remindButton; // @synthesize remindButton=_remindButton;
@property(retain, nonatomic) NSButton *rateButton; // @synthesize rateButton=_rateButton;
@property(retain, nonatomic) NSTextView *descriptionField; // @synthesize descriptionField=_descriptionField;
@property(retain, nonatomic) NSTextField *titleField; // @synthesize titleField=_titleField;
- (void).cxx_destruct;
- (void)openRatingsPageInAppStore;
- (void)hideRateWindow;
- (void)rightButtonSelector:(id)arg1;
- (void)leftButtonSelector:(id)arg1;
- (BOOL)offerToRateAtPoint:(unsigned long long)arg1;
@property(nonatomic) unsigned long long pointsCount;
- (BOOL)isNewVersion;
- (void)askToRate;
- (void)generateUsedPoint;
- (unsigned long long)usedPoint;
- (id)ratingsURL;
- (void)updateMessageTtitle;
- (void)awakeFromNib;
- (void)initialize;
- (id)initWithWindowNibName:(id)arg1 owner:(id)arg2;
- (id)init;

@end

Looks like the XWRate class is a singleton, since it has a static + (id)instance; method. To disable the pop-up, it helps to be able to trigger it reliably without waiting for whatever time period the app does. Looks like the - (void)askToRate method might just do that. So, I fired up the app in lldb, lldb /Applications/Be\ Focused\ Pro.app, started it, (lldb) run, and the stopped execution with ctrl-c after all the startup messages. Then to see if these methods do what I think they do, I used lldb expression evaluation to run the askToRate method.

(lldb) e XWRate* $inst = [XWRate instance]
(lldb) e [$inst askToRate]

And, it worked! The terrible pop-up showed up. The app became unresponsive when I tried to click the button, but that’s probably a side-effect of breaking execution and running the method with the app’s event loop running. Back in Hopper, I took a look at the askToRate disassembly. Hopper makes it really easy to search for Objective-C nethod names directly in the left panel.

Finding the askToRate method in the disassembly

Looking in the disassembly, this section looks particularly guilty since it calls runModalForWindow which presumably causes the pop-up to appear.

000000010009e02d         mov        rsi, qword [0x1003ce920]                    ; @selector(runModalForWindow:), argument "selector" for method _objc_msgSend
000000010009e034         mov        rdi, r12                                    ; argument "instance" for method _objc_msgSend
000000010009e037         mov        rdx, rbx
000000010009e03a         call       r13

Alright, looks like if the call r13 is turned into a nop, the pop-up shouldn’t show up. So, next I created a copy of the app, cp -r /Applications/Be\ Focused\ Pro.app ~/Desktop/. Then, I removed code-signing from the copy so we can modify it without triggering bad things, codesign --remove-signature Be\ Focused\ Pro.app. Then I opened the copy up in Hopper, and used the Modify->Assemble Instruction menu to replaced the call r13 with three nop instructions. Then, I saved a new executable with the modifcations.

Replace the modal window call with nops

Now to test it, I did the same procedure as before with lldb:

(lldb) e XWRate* $inst = [XWRate instance]
(lldb) e [$inst askToRate]

And… no pop-up! Life is good now.

Comments

Atlys reset shenanigans

I’ve been playing around the last few days building a Microblaze processor system on the Digilent Atlys Spartan 6 board. I think my end goal is to boot Linux on it–but for now I’ll settle for booting Das U-Boot. It’s definitely been an interesting journey, since I decided against using the Xilinx-specific git repositories for everything, and instead I used mainline checkouts wherever possible. In another post I’ll go through the hell it took to compile a working GCC with Glibc for little-endian Microblaze. But, in this post I wanted to point out something a little more devious.

In the Atlys board reference manual, you might see something like this: “One pushbutton has a red plunger and is labeled “reset” on the PCB silkscreen this button is no different than the other five, but it can be used as a reset input to processor systems.” And below it is a diagram like this:

Digilent Atlys manual section about push buttons

Well, both of these are lies. I spent so long trying to figure this out, a lot of it because the Xilinx system reset IP block will start running your code for a little while before the reset triggers and the system does nothing. On top of that, it’s not particularly deterministic, so I observed random behavior–not exactly conducive to debugging! After awhile, I knew the problem was with the reset circuit because the program would execute correctly if I held down the reset button. Also, it seemed odd that the processor did not reset itself after configuration, like it should have according to most sources I read. And the most damning evidence of all, the schematic! Here are the regular push-buttons:

Digilent Atlys push buttons schematic

And here is the reset button:

Digilent Atlys reset button schematic In case you are wondering, like I was, what the second reset button net is connected to, it goes to the Cypress USB chip for the Digilent programming interface.

That’s an active low reset! To fix the problem in the Xilinx XPS tool, flip the extern reset polarity on the proc_sys_reset block to “0”. And, you also have to flip the interrupt controller reset input polarity as well. Oh, and that will still give you a platform generator DRC error until you open the project .mhs file and change the external reset port RESET_POLARITY option to 0.

Now my system can send characters on the UART and correctly delay turning on some LEDs using simple delay loops. However, now I’m facing another fun problem: I can send data over the UART from the Microblaze, but so far I have been unable to receive data on the Microblaze. I’m using the Xilinx uartlite core, and the receive FIFO data valid bit in the status register never changes! I tried several different baud rates, checked all the .UCF constraints, and checked all the register addresses. Anyway, once I solve that problem, I can load a Das U-Boot image into the QSPI flash on the board, and hopefully start up the U-Boot console. Compiling U-Boot and configuring it is also a lot of fun, but I’ll write about that later.

Comments

Gameslab high-level design

I finally decided I might as well make an overall block diagram of the Gameslab system. I was working on changing the design schematics and realized I forgot a couple things. So, I made this block diagram to use as a checklist–makes sure I don’t forget pieces. It also helped to figure out which peripherals would connect to the Zynq PS versus the PL. For example, the LCD touch controller was originally connected to PS I2C pins, but the PL has to be enabled to use the LCD anyway. So, it made more sense to route the touch screen I2C bus through the EMIO.

Gameslab block diagram In the Zynq SOC, PS refers to the ARM Cortex A9 sub-system, and PL refers to the programmable logic section. EMIO is a function for passing PS I/O through the PL fabric. Grey blocks will be implemented in programmable logic.

After thinking about what a pain it would be to monitor voltages and currents with the Zynq XADC and do power management with the huge ARM PS with U Boot and Linux, I added a system management controller to the design (SMC). I realized that something had to monitor and control the power-up sequence, and only boot the Zynq when the battery status allows. Doing this with the XADC in the Zynq would require booting the entire processor and PL section, and possibly loading a bitstream–all that just to check the battery voltage. In addition, I wanted to use a momentary slide switch for controlling power, mostly because they feel nice. However, if I hooked this to the Zynq, it’d have to be on some GPIO interrupt, which means the Zynq would boot by default when power is present, power down, and then wait for the switch interrupt. I’d rather keep the whole Zynq powered down when in standby. I don’t plan on my embedded Linux distro taking long to boot.

So, in this revised design, the SMC is a PIC18 microcontroller with its ADC connected to the battery voltage, system voltages, and a system current sense amplifier. It also controls the battery PMIC through an I2C interface. This way, when power is applied, the SMC starts up and monitors the power supplies. If the slide switch is pulled and sufficient battery charge is available, the SMC triggers the Zynq power-up reset. I chose the PIC18 family because I’ve used them on dozens of previous projects, so they’re familiar. However, any 8-bit micro would do.

For the PMIC, I want a chip that will charge with at least 1.5 A, since I want to use three 2500 mAh lithium polymer batteries. I found a couple TI chips that could work, and I’ll post more details when I go through the power system. I also took a look at loads of switching voltage regulators, and I think I’ve decided on all discrete switchers. There are some large chips that integrate 3-6 switching regulators in one, but they all either have the wrong combination of output voltage ranges, currents, or number of regulators. Plus, I want to individually control each supply. This way, the SMC can properly sequence power for the Zynq, and keep the PL shut-down when not necessary. The LEDs on the SMC will indicate charging and power status. Red will mean it’s charging, green will mean the system is on.

I debated whether I should add Wi-fi to the system, using a TI industrial Wi-fi module on an SDIO interface. For example, the TI WL1807MOD is a high-speed completely integrated Wi-fi module. Add antennas, power, and a passive or two, and it does 80 Mbps 802.11n. However, I think for this first revision, network-over-USB will be fine. USB networking will be much simpler to get working in U Boot and Linux.

I will use the XADC for the thumbsticks though! Originally I had them connected to the SMC as well, since I was already using the ADC there. However, The Zynq has the XADC, and it might be interesting to use custom PL hardware to filter the thumbstick input. The buttons also go through the PL pins. These can either pass through the EMIO to the PS GPIO controller, or the custom PL logic can manage them.

Well, I think I pretty much have a plan now. So time to finish fiddling around with my schematics so I can finish this PCB design.

Comments

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

Gameslab first steps

I bought one of the PSP LCD screens from Sparkfun forever ago, planning on building some kind of game console-like device with it. Slowly, I accumulated more hopeful parts, like the PSP-style thumb-sticks and a touchscreen that fits the LCD perfectly. All these parts are screaming at me to build something cool, so I’m going to build an FPGA based, hand-held game console. From scratch.

Building a hand-held console is going to be very different than my previous game console builds, since the small form-factor requires designing the mechanical and electrical components together. I’m not really a fan of the 3D-printed look for project cases, so I want to use some kind of milled aluminum for the front and back panels. Ideally I can have some engravings and maybe even color fills. I might still use 3D-printing for building some side-walls or grips pieces to make the metal edges more comfortable.

Since I have previous experience with Xilinx FPGAs (specifically the Spartan 6 series), I decided on a Spartan 6 part: the XC6SLX25. This is the largest logic cell count available in a decently solderable package. The FTBGA package is 1mm pitch, which is doable with OSH Park PCBs and a toaster oven. Other than the FPGA, the console requires battery charging, power regulation, memory (probably DDR), LCD interface, touchscreen interface, audio (headphones and maybe speakers), thumb-stick interfaces, SD card, some buttons, and maybe Wi-Fi.

Some 3D CAD modeling

I decided to model most of the mechanical components using SolidWorks (it’s what I know how to use). I’ll need to position all of the components before I can design the PCD. The LCD connectors, thumb-stick connections, buttons, battery connectors, power connectors, and SD card slot all depend on the overall mechanical layout. And, there is not going to be a lot of spare room.

Model of the Gameslab LCD part Model of the Gameslab thumbstick part Left: the LCD CAD model; right: the thumb-stick CAD model.

Planning

Several parts of the electrical design will be tricky; I expect designing the FPGA power regulation and DDR interface will be the most painful. To alleviate some of the pain, I’m using the Digilent Atlys board schematics as a reference. However, I have to be careful, since the Atlys was designed to run off of a wall wart. The Gameslab needs to have decent battery life running on rechargeable batteries. For example, the DDR2 address lines on the Atlys are parallel terminated with ~50 Ω resistors.There are 13 address lines, and each line swings between 0 V and 1.8 V. Therefore, each address line will have a DC power dissipation of approximately 16.2 mW. Together, that’s 210 mW! I plan on using one or two lithium polymer batteries, maybe 2000 mA each at 3.7 V; for one cell, that’s 7.4 Watt-hours. I want 4-5 hours of battery life, so system dissipation should be around 1.5 W, and 210 mW is 13.4% of that–just for termination!

The Atlys board uses some really nice Linear Technology switching regulators for the 3.3 V rail, 1.8 V rail, and the 0.9 V DDR2 termination reference. I don’t feel like paying $7 per regulator, so I’m going to do some shopping. I already found a replacement for the LTC3413 (the DDR2 0.9 V reference regulator). On Digikey, the LTC3413 costs $7.94 per unit (in onesey-twosey quantities). The NCP51199 DDR2 termination regulator from ON Semi costs just $0.70.

I’ve also given the FPGA configuration at startup some thought. FPGAs are designed to load their configuration from an external memory (SPI or parallel flash typically) at startup. However, wouldn’t be interesting if every game could have completely custom hardware? That’s why I’m looking at including a microcontroller to interface with the SD card and perform the FPGA configuration and re-configuration. The microcontroller can load a default FPGA configuration that will present a game selection screen, and then once a game is selected, the FPGA will communicate with microcontroller, instructing it to re-configure the FPGA with the selected game image. The XC6SLX25 has 6,440,432 configuration bits (The total number of configuration bits was hard to find. It’s buried in this document at the end of page 75), and with a maximum configuration serial slave clock of 80 MHz, that would take 80 ms to reconfigure–about 5 frames at 60 FPS.

Since power is going to be a big design issue, I thought I’d do a quick estimate of the likely power consumption. I have a previous FPGA design for the Atlys board that contains a Microblaze soft processor, DDR2 interface, and an HDMI output. Since the Atlys board uses an FPGA almost twice the size and the design is much higher speed, the estimate should be conservative. I used the Xilinx XPower Analyzer, and came up with the values below:

Supply Rail Voltage Current Power
VCCINT 1.2 V 0.370 A 0.444 W
VCCAUX 3.3 V 0.108 A 0.356 W
VCCO33 3.3 V 0.022 A 0.073 W
VCC018 1.8 V 0.196 A 0.353 W
    Total: 1.226 W

Obviously this is going to be pushing the limit of a single 2000 mAh lithium polymer cell. The Gameslab will likely require two of the cells, since 1.226 W eats almost 82% of the power budget already.

Comments
Copyright © 2017 Craig J Bishop