Adc_mul does not work as I expected


Hi,
I tried to use Husky with different adc_mul factor. I found the recorded data with adc_mul=1 and adc_mul=4 are just the same. This is not what I expected: I expected that the lower plot (adc_mul=4) shall be a “zoom-in” of the above one (adc_mul=1) because husky records 4 samples per cycle.

The value of adc_clock is 29MHz and target is 7.37MHz. The clock setting looks good so did I misunderstand anything?

Yes, it should be. Show me your Python code?

The code is modularized so it is hard to paste everything here. I share some parts I think related.

def set_clock(
		self, 
		clock_speed:int=7.38E6, 
		clock_source:str='clkgen_x1',
		husky_clock_src:str='system',
		husky_clock_mul:int=1):
		"""
		Set the clock of CW's scope and the baud rate of 
		the target to read the values from it.
		Currently, there are 2 options of clock_source 
		for CW Lite: clkgen_x1 and clkgen_x4 

		For the Husky scope, it is mainly used 
		scope.clock.clkgen_src and scope.clock.adc_mul for its
		adc_freq; The clock.adc_src is only for backward compatibility.

		2 options for husky_clock_src: system and external.
		"""
		self.scope.clock.clkgen_freq = clock_speed

		if self.name == 'Husky':
			self.scope.clock.clkgen_src = husky_clock_src
			self.scope.clock.adc_mul = husky_clock_mul
			time.sleep(0.1)
			print('adc clock :', self.scope.clock.adc_freq)
			print('target clock:', self.scope.clock.clkgen_freq)
		else:
			self.scope.clock.adc_src = clock_source
		
		self.target.baud = int(clock_speed / 7.37E6 * 38400)

def set_adc(
		self, 
		decimate:int=1, 
		samples:int=20_000, 
		offset:int=0,
		enable_stream:bool=False,
		bits_per_sample:int=12):
		"""Set ADC of the scope"""

		self.scope.default_setup()
		if self.name == 'Husky':
			self.scope.adc.stream_mode = enable_stream
			# self.scope.adc.bits_per_sample = bits_per_sample
		self.scope.adc.decimate = decimate
		self.scope.adc.samples = samples
		self.scope.adc.offset = offset
### instSampleMultiplier decides how many power samples
### I need for capturing for each instruction.  
instSampleMultiplier = 4

### The product of number of instruction number and
### the inst/sample multiplier is the total power samples
### for capturing
targetPowerSamples = instCount * instSampleMultiplier

### The maximum buffer size of ChipWhisperer Husky
### is 100k samples. If the target power samples 
### are larger than the buffer size, we will need
### to repeat the same procedure for multiple times
bufferSize = 100_000
numChunks = targetPowerSamples // bufferSize
numChunks = (
	numChunks+1 
	if targetPowerSamples % bufferSize 
	else numChunks)
cw = CWrapper(
	platform=PLATFORM,
	name='Husky')
cw.reset()

offset = 0
cw.set_clock(
	clock_speed=7.38E6, 
	husky_clock_src='system',
	husky_clock_mul=instSampleMultiplier)

cw.set_adc(
	decimate=1, 
	samples=bufferSize, 
	offset=offset)

cw.program_target(
	hex_name=HEX_FILENAME)

fullPowerTraces = np.zeros(numChunks * bufferSize)
print('fullPowerTraces.shape:', fullPowerTraces.shape)
print('numChunks:', numChunks)

for n in range(numChunks):

	output, powerTrace = cw.capture_by_inference(
		input_data=[1],
		write_input_register_byte='p',
		output_size=4,
		read_output_register_byte='r',
		capture_power=True)
	print(powerTrace.shape)
	fullPowerTraces[offset:offset+bufferSize] = powerTrace

	offset += bufferSize

	cw.set_adc(
		decimate=1, 
		samples=bufferSize, 
		offset=offset)

	## lapse shall set with value at least higher than 0.001
	cw.reset(lapse=0.1)

This is a bit hard to follow and yet incomplete (e.g. instSampleMultiplier isn’t defined).
Please reduce this to a minimal reproducible example… something like this:

  • connect to Husky and target
  • capture trace with adc_mul = 1
  • capture trace with adc_mul = 4
  • compare results

I suspect that in doing so you’ll find your issue!

Ok, I will make a simple code to test again

I simplified the code a bit, but the issue remains

import struct
import numpy as np
import chipwhisperer as cw
import chipwhisperer.capture.targets as CWTargets

scope = cw.scope()
target = cw.target(
	scope, 
	CWTargets.SimpleSerial)
mul = 4
scope.clock.clkgen_freq = 7.38e6
scope.clock.clkgen_src = 'system'
scope.clock.adc_mul = mul
target.baud = int(7.38e6 / 7.37E6 * 38400)

scope.default_setup()
scope.adc.decimate = 1
scope.adc.samples = 100_000
scope.adc.offset = 0

cw.program_target(
	scope,
	cw.programmers.STM32FProgrammer,
	'main-CWLITEARM.hex')

inputBytes = bytearray([])
inputData = [1]
for d in inputData:
	d = struct.pack('i', d)
	inputBytes += d

target.simpleserial_write('p', inputBytes)

scope.arm()
scope.capture()
trace = scope.get_last_trace()

outputData = target.simpleserial_read('r', 4, timeout=0)

np.save(f"test_x{mul}.npy", trace)

scope.default_setup() changes a bunch of your scope settings, including scope.clock.adc_mul.
So, no matter what you set mul to, the capture from this code will be with scope.clock.adc_mul = 4.

This is why we made scope.default_setup() tell you what it’s changing:
If you run the code interactively, you would see:

>>> scope.clock.adc_mul = 1
>>> scope.default_setup()
scope.clock.adc_mul                      changed from 1                         to 4                        
scope.clock.adc_freq                     changed from 7370129.87012987          to 29480519.48051948        
scope.clock.extclk_tolerance             changed from 1715241718.189958         to 19629383676.79548       

Hi, thanks for the reply.

I check your code: if I set scope.clock.adc_mul = 4, even the scope.default_setup() is call after, scope.clock.adc_mul remains 4.

In addition, if I comment out the defaut_setup(), the program complains:

OSError: Could not detect STM32F, check connections, BOOT MODE entry setup

Note: My ChipWhisperer version is 5.7 and the result of scope.fw_version is {'major': 1, 'minor': 4, 'debug': 0}.

Hi,
After some changes, I now can get the different result. (Note: not so sure this is the correct or not)

The change I made is as following:

scope = cw.scope()
target = cw.target(
	scope, 
	CWTargets.SimpleSerial)
scope.default_setup()
cw.program_target(
	scope,
	cw.programmers.STM32FProgrammer,
	'main-CWLITEARM.hex')

mul = 4
scope.clock.clkgen_freq = 7.38e6
scope.clock.clkgen_src = 'system'
scope.clock.adc_mul = mul
target.baud = int(7.38e6 / 7.37E6 * 38400)

scope.adc.decimate = 1
scope.adc.samples = 100_000
scope.adc.offset = 0

scope.io.nrst = "low"
time.sleep(0.05)
scope.io.nrst = "high"

I first use default_setup before program_target so that the connection between host and target can work. Then I change the clock and adc speed after the program process is finished. Once this change is made, I reset target by rewriting nrst pin.

However, I am still a bit confused that when the clock update really happen. Does it occur once I assign 7.38e6 to scope.clock.clkgen_freq? Or everything only takes effect when the target is reset by nrst?

scope.default_setup() does a few things that most of our targets need, like turning on the clock to the target on HS2. The idea is to:

  1. connect to the scope
  2. call default_setup()
  3. then customize anything you want.

You can (and should) have a look at any of our Jupyter tutorials if you have a doubt on how to set up your target for capture.

Setting scope.clock.clkgen_freq has an immediate effect. Some targets may require a reset after its input clock is changed.

I’m working with a ChipWhisperer Husky and trying to configure the clock settings. Specifically, I want to understand the role of adc_mul in the sampling frequency and why I’m encountering a warning when setting it higher than 4.

Here’s a snippet of my code:

if TARGET_PLATFORM in ['CW312T_A35', 'CW312T_ICE40']:
    scope.clock.clkgen_freq = 7.37e6
    scope.io.hs2 = 'clkgen'
    if scope._is_husky:
        print("Husky")
        scope.clock.clkgen_src = 'system'
        scope.clock.adc_mul = 4
        scope.clock.reset_dcms()
    else:
        scope.clock.adc_src = "clkgen_x4"
    import time
    time.sleep(0.1)
    target._ss2_test_echo()

However, when I attempt to set adc_mul higher than 4, I receive the following warning:

(ChipWhisperer Scope WARNING|File ChipWhispererHuskyClock.py:663) 
                Could not calculate pll settings for the requested frequency (7363636); 
                generating a 7333333 clock instead.
                It may be possible to get closer to the requested frequency
                with a different adc_mul.
                It may also be possible to get closer to the requested
                frequency if you set scope.clock.pll._allow_rdiv to True;
                however this can result in an inconsistent clock phase between
                the target and ADC clocks; use at your own risk!

(ChipWhisperer Scope WARNING|File ChipWhispererHuskyClock.py:703) 
                Target clock may drop; you may need to reset your target.

Questions:

  1. What exactly does adc_mul control? Does it determine the sampling frequency, or is it related to something else?
  2. Why am I seeing this warning when setting adc_mul above 4? Are there hardware/PLL limitations preventing a higher value?
  3. Can I safely set adc_mul higher than 4? If so, are there any workarounds or configurations that would allow it?

You may set scope.clock.adc_mul to whatever integer value you like, as long as the resulting sampling clock frequency is within the supported range (5 - 200 MHz). If you exceed this range, you will get a specific warning about that.

Here you are getting two warnings.

The first tells you that although you have requested a 7.37 MHz clock, the best we can do is 7.33 MHz. You can request any frequency you like; we try to find clock generation parameters that generate a clock frequency as close as possible to what you requested, and if it’s further than some small delta, the warning is issued.

The second warning is telling you that in setting the clocks that you requested, the target clock may have been dropped (left idle) for a brief moment. For certain targets, this may cause them to lock up and require a reset.

Both of these warnings are simply informational; they are marked as “warnings” to ensure you don’t ignore them.

1 Like

Thank you for the reply. It worked