Help with Setting Trigger for Clock Glitch Attack on CW305 using CW1173

Hello,

I’m trying to perform a clock glitch attack on the FPGA board CW305, using the capture board CW1173 ChipWhisperer-Lite.

In my Verilog project, I configured the circuit I’m attacking to have a trigger output, which I connected to FPGA pin T14 (assigned to tio_trigger in the constraints file). This pin is part of the 20-pin connector and corresponds to TIO4, as described here:
https://chipwhisperer.readthedocs.io/en/latest/Capture/20-pin-connector.html

This setup seems to be working. I also wired led1 to turn on when this pin is active, and this works correctly during debugging.

However, when I configure the clock glitch attack in Python, I’m unable to get the glitch to trigger based on this pin. Here’s the relevant portion of my Python code:

Specifically, I used the line:

scope.trigger.triggers = "tio4"

I got this from the following forum post:

Unfortunately, it doesn’t seem to have any effect. I don’t observe any glitches being triggered. Also, I couldn’t find detailed documentation on how to properly set the trigger pin using the Scope API on the CW documentation site. Could someone clarify the correct way to set the external trigger pin?

Additionally, the circuit I’m testing is a relatively slow finite state machine (FSM) that performs multiplications and additions across several stages on two inputs. If this type of circuit is not suitable for clock glitching attacks on an FPGA, I’d appreciate any feedback. Since there aren’t many tutorials available yet, I’m trying to start with a simple design to learn the basics of clock glitching.

Thanks in advance for your help!

How are you configuring scope.glitch?

Any circuit should be vulnerable to clock glitching, unless it has explicit protections against it.

Protection can be very simple: e.g. using a PLL to clean up the glitchy clock.

This is the configuration I used. Some lines are commented out because I tried different settings:

#Basic setup
scope.glitch.clk_src = "clkgen" # set glitch input clock

scope.glitch.output = "clock_xor" # glitch_out = clk ^ glitch
#scope.glitch.output = "clock_only" #no glitch

scope.glitch.trigger_src = "ext_single"
#scope.glitch.width = 40
#scope.glitch.offset = 10
scope.glitch.width = 25
scope.glitch.offset = 25
# We trigger from "tio4" input
scope.trigger.triggers = "tio4" 
scope.io.hs2 = "glitch"  # output glitch_out on the clock line
print(scope.glitch)

Let me know if you would like to see the full Python notebook or the Vivado project for the circuit I am targeting. I would be happy to share them if it helps.

And how are the glitches getting triggered? With trigger_src set to “ext_single”, glitches won’t be issued unless the scope is armed: Scope API — ChipWhisperer Documentation

I corrected the issue, but the trigger is still not being detected, and I receive the following error message:
ChipWhisperer Scope WARNING|File _OpenADCInterface.py:732) Timeout in OpenADC capture(), no trigger seen! Trigger forced, data is invalid. Status: 0b

I was able to successfully perform the glitch attack on the AES circuit provided by ChipWhisperer for the CW305 board. I configured the trigger pin (tio_trigger) in Vivado to activate whenever my circuit begins its operation, as done in the example. However, the trigger is still not being detected.

Here is the full script for the attack:

import time
import numpy as np
from chipwhisperer.common.traces import Trace
import chipwhisperer as cw
from tqdm import trange
import random
import string

FPGA_ID='100t'

def reboot_flush():
    scope.io.nrst = False
    time.sleep(0.05)
    scope.io.nrst = "high_z"
    time.sleep(0.05)
    target.flush()

    
scope = cw.scope()

scope.gain.db = 25
scope.adc.samples = 129
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 20E6
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "clkgen"

# program target:
bitpath = r"C:\Users\julia\Desktop\Vivado_Projects\Fault_Injection\clock_glitch_target_V4\clock_glitch_target_V4.runs\impl_1\cw305_top_gl.bit"
target = cw.target(scope, cw.targets.CW305, fpga_id=FPGA_ID, force=True, bsfile=bitpath)

target.vccint_set(1.0)
# we only need PLL1:
target.pll.pll_enable_set(False)
target.pll.pll_outenable_set(False, 0)
target.pll.pll_outenable_set(False, 1)
target.pll.pll_outenable_set(False, 2)

# run at 20 MHz:
target.pll.pll_outfreq_set(20E6, 1)

# 1ms is plenty of idling time
target.clkusbautooff = False
target.clksleeptime = 1

# ensure ADC is locked:
scope.clock.reset_adc()
assert (scope.clock.adc_locked), "ADC failed to lock"

target.usb_clk_setenabled(1)


scope.glitch.clk_src = "clkgen" # set glitch input clock
scope.glitch.output = "clock_xor" # glitch_out = clk ^ glitch
scope.glitch.trigger_src = "ext_single" # glitch only after scope.arm() called
scope.glitch.width = 10
scope.io.hs2 = "glitch"  # output glitch_out on the clock line
scope.io.glitch_lp = False
scope.glitch.ext_offset = 300 # started at 2
scope.glitch.repeat = 2 # started at 2

scope.adc.timeout = 0.1

import struct
import numpy as np
from tqdm import trange

# Experiment parameters
glitch_values   = [
    (1.0, 0.5),
    (1.0, 1.0),
    (2.0, 5.0),
    (3.0, 8.0),
]
rounds_per_point = 5

glitch_outputs = []
print("Running selected glitches")
expected_result_1= 14
expected_result_2= 7
for w, off in glitch_values:
    # Configure the current glitch
    scope.glitch.width      = w
    scope.glitch.ext_offset = off

    # (w,off)
    result = {"width": w, "offset": off, "resets": 0, "successes": []}
    glitch_outputs.append(result)

    for _ in range(rounds_per_point):
        # Si el scope quedó disparado, reiniciamos
        if scope.adc.state:
            result["resets"] += 1
            reboot_flush()
            
        # Armamos el scope para esperar el trigger en TIO4
        scope.arm()

        # Cargamos entradas
        a = 2  
        b = 1  
        data = (b << 4) | a
        target.fpga_write(0x00, [data])

        # Disparamos el circuito
        target.fpga_write(0x01, [0x01])  # activar start

        # Capturamos la traza y el glitch
        ret = scope.capture()

        # Leemos resultados
        # Leer resultado de multiplicación (9 bits)
        result1_low  = target.fpga_read(0x02, 1)[0]  # bits [7:0]
        result1_high = target.fpga_read(0x03, 1)[0] & 0x01  # bit [8]
        result_1 = (result1_high << 8) | result1_low
    
        # Leer resultado de suma (5 bits)
        result_2 = target.fpga_read(0x04, 1)[0] & 0x1F

        if ret:  # timeout → no llegó trigger
            result["resets"] += 1
            reboot_flush()
        else:
            # Comparamos la suma con el valor esperado
            if result_1 != expected_result_1:
                result["successes"].append(result_1)
                print(f"Successful glitch: sum={result_1}, setting=(w={w}, off={off})")
            if result_2 != expected_result_2:
                result["successes"].append(result_2)
                print(f"Successful glitch: sum={result_2}, setting=(w={w}, off={off})")

You won’t get a glitch if you don’t trigger the scope, so you must first sort out why you’re not getting a trigger.
T14 is indeed the FPGA pin which is connected to the “tio4” line on the 20-pin connector. You can verify whether it’s going high by probing the “TRIG” testpoint near the bottom-right corner of the CW305.
It’s possible that your scope.adc.timeout = 0.1 is too aggressive; try increasing it to 1 or 2 seconds.