Improving SNR and Mitigating Ground Bounce during CPA on I2C Devices (CW313 + Husky)

Hello everyone,

I am a beginner in the SCA field, currently conducting academic research on I2C state-machine behavior using the ChipWhisperer-Husky Starter Kit. I am working with a custom breadboard setup alongside the CW313 Interposer Board.

My research involves analyzing the power consumption of an EEPROM (AT88SC family) during specific I2C transactions. To study the reset state of the chip’s internal logic, my experiment requires power-cycling the target device abruptly before an I2C STOP condition is generated.

While the logic side of my Python script works, I am facing severe analog and synchronization issues (poor SNR and ghost peaks) and would appreciate some guidance from the community on how to improve my hardware setup.

1. Hardware Setup (CW313 ↔ Breadboard)

Since I am bit-banging the I2C protocol, I am using the CW313 to bridge the Husky and my breadboard:

  • Target: 8-lead SOIC connected via micro test clips to a breadboard.

  • I2C Comm: CW313 GPIO01 (Husky TIO1) → SDA | CW313 GPIO02 (Husky TIO2) → SCL.

  • Pull-up Resistors: Two 4.7kΩ pull-up resistors on SDA and SCL. I am using CW313 GPIO03 (driven HIGH via Husky TIO3) to feed the 3.3V rail powering these pull-ups.

  • Power Measurement: I am using the CW313’s onboard shunt. The SHUNTL pin goes directly to the target’s VCC. I am using Husky’s 3.3V Target Power (scope.io.target_pwr) to power the board. Toggling this pin allows me to power-cycle the target.

2. The Analog Problem

Because I am abruptly power-cycling the target during the experiment:

  1. Ground Bounce / PLL Issues: When target_pwr is set to False, the resulting ground bounce causes the Husky’s ADC/Glitch LEDs to blink. The FPGA reports target clock issues (even though clkgen_src = 'system').

  2. Correlation Issues: I am applying FFT alignment on the traces. However, the CPA (Pearson Correlation with Hamming Weight) models are returning erratic peaks (e.g., 0x12, 0x7A). I suspect the power cut transient is completely destroying my SNR, or the bit-banged I2C clock pauses are misaligning the capture window.

3. The Implementation

Here is the core logic I am using. I am arming the scope at the 8th bit of the payload, right before the ACK cycle.

import chipwhisperer as cw
import time
import numpy as np
import scipy.signal

1. SETUP

scope = cw.scope()
scope.default_setup()
time.sleep(0.5)

Shielding the Husky clock for I2C Bit-Banging

scope.clock.clkgen_src = ‘system’
scope.clock.clkgen_freq = 7372800
scope.clock.adc_mul = 4
scope.glitch.enabled = False
scope.clock.reset_adc()
time.sleep(0.5)
scope.errors.clear()

scope.trigger.triggers = “tio4”
scope.trigger.module = “basic”
scope.gain.db = 25
scope.adc.samples = 15000
scope.adc.offset = 0

Power the pull-ups via CW313 GPIO03

scope.io.tio3 = “gpio_high”

def sda(bit): scope.io.tio1 = “high_z” if bit else “gpio_low”
def scl(bit): scope.io.tio2 = “high_z” if bit else “gpio_low”

def i2c_experiment_capture(data_byte):
for i in range(7):
sda((data_byte >> (7 - i)) & 1)
scl(1); scl(0)

sda(data_byte & 1)
scl(0) 

# Arm the scope right before the operation
scope.arm()      
scope.io.tio4 = "gpio_high"
scope.io.tio4 = "gpio_low"

scl(1)
scl(0) 
time.sleep(0.002) # Wait for recording to finish

sda(1); scl(1)
ack = scope.io.tio_states[0]

# Hold SCL low to prevent I2C STOP condition before power cycle
scl(0) 
return ack

2. CAPTURE LOOP

num_iterations = 256
traces = np.zeros((num_iterations, scope.adc.samples))

for iteration in range(num_iterations):
temp_traces = 


for _ in range(10): # Captures per iteration
    scope.io.target_pwr = True
    time.sleep(0.05) 
    sda(1); scl(1)
    time.sleep(0.01)

    # Standard I2C Start and setup bytes
    sda(0); scl(0)             
    # ... (sending setup bytes) ...
    
    i2c_experiment_capture(iteration)
    
    ret = scope.capture()
    if not ret:
        trace = scope.get_last_trace()
        if trace is not None and (np.max(trace) - np.min(trace)) > 0.01:
            temp_traces.append(trace)
    
    scope.errors.clear() 
    
    # Power cycle target to study state machine reset
    scope.io.target_pwr = False     
    time.sleep(0.05) 
        
if len(temp_traces) > 0:
    traces[iteration] = np.mean(temp_traces, axis=0)

(Standard FFT alignment and CPA math follows…)



My Question: Is this reasoning appropriate for measuring analog data during state-machine interruptions? I would greatly appreciate suggestions from experienced members on how to adjust the Husky ADC settings or improve the hardware measurement to prevent the ground bounce from corrupting the SNR.

Thanks in advance!

Hello and welcome.

This doesn’t give me a full picture of your setup; a schematic would be useful.

What exactly is the error? Run print(scope.errors) to find out.

What exactly are the “target clock issues”?

Hello, and thank you for taking the time to reply!

To give you a better picture, my target is an Atmel AT88SC25616C (CryptoMemory). My goal is to extract a 24-bit password by exploiting a timing/power leakage in the EEPROM’s charge pump activation, combined with a “Tear-Off” attack to prevent the internal security counter (PAC) from decrementing.

Here is the breakdown of the setup, the errors, the progress I’ve made, and the analog wall I’ve hit.

1. The Setup & “Schematic”

I am using a custom breadboard connected to the CW313 Interposer.

  • Target: AT88SC25616C (8-lead SOIC).

  • VCC: Routed directly through the CW313 SHUNTL for power analysis. The power is supplied by Husky’s target_pwr.

  • I2C Bus: Bit-banged. TIO1 → SDA, TIO2 → SCL.

  • Pull-ups: Two 4.7kΩ resistors, powered by TIO3 (driven HIGH).

  • Trigger: TIO4 is pulsed HIGH->LOW right before I send the I2C STOP condition.

  • The Tear-Off: Exactly 3.5ms after the STOP condition, I hard-cut the power (scope.io.target_pwr = False) to prevent the EEPROM from finalizing the PAC decrement write cycle.

2. The Errors & Target Clock Issues

When I run print(scope.errors) right after the power cut, I typically get an ADC Error (specifically, ADC clipping or phase errors) and sometimes ExtClk Error.

Because I am abruptly dropping target_pwr while the pull-ups and logic lines are still active, it creates a massive ground bounce/transient. Even though I am using scope.clock.clkgen_src = 'system' (the Husky is generating its own clock, not relying on the target), the electrical shock on the CW313 seems to destabilize the Husky’s ADC PLL or trigger logic momentarily, causing the LEDs to flash red.

3. The Attack Progression & The Core Problem

Despite the transient, I managed to capture clean enough traces before the power cut to successfully extract the 1st byte of the password, but the 2nd and 3rd bytes completely broke my mathematical models.

The State-Machine Leakage: When the chip verifies the 3-byte password, it evaluates them sequentially.

  • If Byte 1 is wrong, it immediately fires up the high-voltage charge pump to write to the EEPROM (decreasing the PAC).

  • If Byte 1 is correct, it delays the charge pump ignition to check Byte 2. This creates a beautiful, measurable delay in the power trace (Timing Leakage).

The Success (Byte 1): Using a simple TVLA and a Single-Point DPA (or SAD), I successfully isolated Byte 1 (0x18). The charge pump ignition happens early enough (around sample 30,000 at 29.5 MS/s) that the traces align well.

The Analog Wall (Bytes 2 & 3): The AT88SC relies on an internal, asynchronous RC oscillator running at ~36 kHz. By the time the execution reaches the Byte 2 decision (around sample 50,000+) and Byte 3 (sample 60,000+), the thermal and voltage variations cause massive Cumulative Phase Drift.

When I visually inspect the traces, I can clearly see that the correct Byte 2 (0xC4) holds the baseline voltage steady while all other 255 incorrect guesses suffer a voltage sag (charge pump activating). However, because of the severe jitter and DC offset between captures, standard Time-Domain Analysis (SAD, Variance, CPA, or Threshold Crossing) completely fails. The mathematical models end up latching onto noise spikes or baseline drifts rather than the actual charge pump delay.

4. What I Need Help With

To make this project successful and reliable, I need guidance on two fronts:

  1. Hardware / Measurement: How can I cleanly cut power to the target (Tear-Off) without causing a transient that crashes the Husky’s ADC? Should I use an external MOSFET circuit triggered by a GPIO instead of dropping scope.io.target_pwr directly?

  2. Software / Signal Processing: Standard FFT alignment on the start of the trace doesn’t fix the asynchronous RC drift 60,000 samples later. What are the best practices within the ChipWhisperer ecosystem for aligning traces with severe internal clock jitter? Is implementing Dynamic Time Warping (DTW) or Elastic Alignment the only way out, or is there a better feature-extraction technique for this kind of asynchronous delay?

Thank you again for the support!

Let me first set expectations: we cannot answer all your questions, particularly on the signal processing side. We answer all questions directly related to using our products. We’re happy to help with questions that fall outside of that, so asking such questions is not a problem, but understand that we may not be able to help you with e.g. aligning your traces. Doing this well can require pretty deep knowledge about your setup, your target, and your goals, and that’s well outside the scope of our free support.

Review our Intro to Husky notebook to understand what the flashing red LEDs mean. With regards to ADC clipping error, you can either:

  • adjust scope.gain so that the ADC doesn’t clip
  • choose to ignore the error (use this)

I have no idea what “phase error” might be; what is the exact message?

extclk_error should not happen; please provide the full output of print(scope) when you see it.

I will also say that toggling TIO4 from Python to then trigger on it is far from ideal, we wouldn’t recommend it unless there is really no other way! Triggering from Python like this means that you will have very inconsistent timing (and therefore potentially tricky trace alignment problems). Why not trigger on the I2C line, using either the basic trigger or the edge counter trigger?

I’m also confused about how you capture the traces. In your code above, you arm the scope once, then call scope.capture() multiple times in a loop: that is not the correct way to do it (and probably the cause of some of the errors you see?). You must arm the scope each time before scope.capture(). Have a look at how cw.capture_trace() works.

Finally, you may be interested in using our brand-new “hardware bitbanger” feature, which was added to the develop branch just last week. We will publish demo notebooks shortly, but for now you can look at the API. We’ve not used it for I2C yet but it should work very well for that. We’ve used it for SWD and 1-wire.

CW313: How to cleanly power-cycle an external target without back-powering through SHUNTL?

Hi,

I’ve been working on this for over a week trying multiple approaches and I’m stuck on what I believe is a CW313 power architecture question.

Setup

  • ChipWhisperer-Husky + CW313 + CW308-to-CW312 Adapter (NAE-CW312TADPT)
  • External I2C target on breadboard via SOIC-8 test clip
  • I2C bit-banged (TIO1=SDA, TIO2=SCL)
  • External shunt resistor between CW313 SHUNTL pad and target VCC
  • SMA cable from CW313 to Husky MEASURE port

The problem

My experiment requires fast, repeatable power-cycling of the external target (cutting and restoring VCC within a few milliseconds). I tried two approaches:

Approach 1: scope.io.target_pwr = False

This works for I2C communication, but the power ramp-down is too slow for my timing requirements. The target passes through a brownout zone that corrupts its internal EEPROM state.

Approach 2: External P-channel MOSFET (IRF9540N) on VCC path, controlled by TIO3

The MOSFET switches in microseconds, which meets my timing requirements. However, I2C communication fails completely (NACK on every command) whenever the MOSFET is in the circuit.

After debugging, I found the root cause: the target has two power paths:

  1. MOSFET path: 3.3V → IRF9540N → shunt → target VCC (controlled by TIO3)
  2. SHUNTL path: target_pwr → CW313 onboard shunt → SHUNTL pad → wire → target VCC (always on)

When the MOSFET is OFF, the target stays partially alive through the SHUNTL measurement path. It never fully resets, and I2C communication breaks. I confirmed:

  • Without MOSFET, using only target_pwr: I2C works every time
  • With MOSFET in circuit: I2C always fails (ACK=1), regardless of power-up sequencing
  • The issue persists even when forcing SDA/SCL LOW before power-up and trying various timing sequences between target_pwr and the MOSFET

My question (CW313/Husky specific)

What is the recommended way to implement fast, clean power-cycling of an external target connected through the CW313, given that the SHUNTL measurement path back-powers the target and prevents a full reset?

Is there a way to electrically isolate the SHUNTL path during power-off, or a recommended circuit topology for using an external power switch with the CW313 that avoids this dual-path issue?

Thanks!

I can’t answer that without a proper schematic of your circuit. It doesn’t need to be fancy, hand-drawn is fine! But it needs to be accurate, and it needs to be a picture. You keep trying to describe your circuit with words in a way that leaves too much open to interpretation.

Hi jpthibault,

Thank you for your patience — I apologize for not being clear enough with words. You’re absolutely right that a schematic is needed. I’ve attached one below showing the full circuit with both power paths.

The key issue is visible in the diagram: Node B (target VCC) receives power from two paths simultaneously — one through the MOSFET (which I can control) and one through the SHUNTL measurement wire (which is always on when target_pwr is active). This prevents the target from fully resetting when I turn off only the MOSFET.

I’ve been stuck on this for over two weeks now and haven’t been able to make progress. I would really appreciate any guidance on how to properly handle power-cycling in this configuration.

Thank you very much for your time and willingness to help.

POWER PATH 1 (MOSFET — fast, controlled by TIO3):
  3.3V ──► IRF9540N Source ──► Drain ──► [220Ω] ──► Target VCC (Pin 8)

  POWER PATH 2 (SHUNTL — always on when target_pwr = True):
  target_pwr ──► CW313 LDO ──► CW313 onboard shunt ──► SHUNTL pad
                                                            │
                                                         wire on
                                                        breadboard
                                                            │
                                                     ──► Target VCC (Pin 8)

  PROBLEM: When MOSFET is OFF, Path 2 still powers the target.
           Target never fully resets. I2C state machine gets stuck.

 ══════════════════════════════════════════════════════════════════════

  ACTIVE CONNECTIONS SUMMARY:
  ─────────────────────────────────────────────────
  CW313 Pin         →  Destination
  ─────────────────────────────────────────────────
  3.3V               →  IRF9540N Source
  TIO1 (GPIO1)       →  Node SDA (target Pin 5)
  TIO2 (GPIO2)       →  Node SCL (target Pin 6)
  TIO3 (GPIO3)       →  IRF9540N Gate
  TIO4 (GPIO4)       →  Node SCL (same as TIO2)
  SHUNTL pad         →  Node B (target VCC side of 220Ω)
  GND                →  Target Pin 4
  ─────────────────────────────────────────────────
  IRF9540N Source    →  3.3V + [10kΩ to Gate]
  IRF9540N Gate      →  TIO3 + [10kΩ to Source]
  IRF9540N Drain     →  Node A (input side of 220Ω)
  ─────────────────────────────────────────────────
  220Ω               →  Between Node A and Node B
  4.7kΩ #1           →  Between Node A and Node SDA
  4.7kΩ #2           →  Between Node A and Node SCL
  ─────────────────────────────────────────────────

Note: Node B is where SHUNTL wire, 220Ω output, and Target VCC (Pin 8) all connect.
This creates the dual power path problem: SHUNTL back-powers the target when MOSFET is OFF.
Pull-ups connect to Node A (MOSFET Drain side) so they only have power when MOSFET is ON.

I’m still confused by your diagram. Where is SHUNTH?

If you don’t want the second power path, why don’t you simply disconnect it?

I’m also doing work with an I2C target at the moment; this is how I’ve wired it:

Here it’s wired as a “normal” ChipWhisperer target, powered via Husky’s target_pwr.

The labels refer to pins on the CW313. In your case, you could simply connect your “Node A” to where I have FILTHP.

Hi jpthibault,

Thank you so much for the detailed schematic and the explanation! It completely clarified the “dual path” issue I was facing. Moving the MOSFET before the measurement block to isolate the I2C pull-up noise from the DPA trace is a brilliant approach.

I have completely rewired my breadboard to match your diagram, effectively creating a single power path controlled by the MOSFET. However, I’ve run into a physical roadblock on the CW313 board itself: the power reaches the measurement block but doesn’t come out the other side.

Here is my exact current setup based on your FILTHP suggestion:

The Setup:

  • JP4: REMOVED (To prevent the CW313’s 3.3V LDO from feeding the target directly and bypassing the MOSFET).

  • MOSFET Source: Connected to the generic 3.3V pin on the CW313.

  • MOSFET Gate: Connected to TIO3 (with a 10kΩ pull-up to Source).

  • MOSFET Drain (Node A): Connected to the SHUNTH pad on the CW313 AND to the I2C 4.7kΩ pull-up resistors.

  • Target Power: A wire going from the SHUNTL pad directly to the Target’s VCC (Pin 8).

The Multimeter Test & The Problem: I wrote a simple script to pull TIO3 LOW (turning the MOSFET ON) and took measurements with my multimeter referenced to the CW313 GND.

  • Source: 3.3V (Power is arriving from Husky)

  • Gate: 0.0V (Husky is successfully pulling it low)

  • Drain / SHUNTH: 3.3V (MOSFET opens perfectly, and power reaches the input of the measurement block!)

  • I2C Pull-ups: 3.3V (I2C lines are correctly powered from Node A)

  • SHUNTL / Target VCC: 0.0V :cross_mark:

My Question: The power completely dies between SHUNTH and SHUNTL. The target remains unpowered. Is it possible that my CW313 board is unpopulated/missing the internal SMD shunt resistor between SHUNTH and SHUNTL? Or is there another jumper (perhaps J4 / Measure) or trace I need to configure/cut to allow the power to flow through the onboard shunt when driving SHUNTH externally via the MOSFET?

Thank you again for your time and guidance!

Are your CW313 jumpers set as shown in the Husky user manual?