Skip to main content

Timing measurements

There are two ways to get numbers out of a capture, and they suit different jobs:

  • Measurements: ask the server for the duration, the frequency or the edge count between two points. Few calls, computed on the server, and saved into the .scana.
  • Raw transitions: read the edges and do the arithmetic yourself. This is what you want for a distribution: every pulse width, not one summary.

Measurements between two markers​

from ikalogic_scanastudio import ScanaStudio

with ScanaStudio.connect() as server:
workspace = server.create("se254")
last = workspace.capture.run(samples=500_000, sample_rate=25_000_000)

clock = workspace.channel("CLK")

# Over the whole capture, without placing markers by hand.
# No `kinds`: the standard timing set.
workspace.measures.between(0, last, channel=clock)

# And separately, the edge count.
workspace.measures.between(0, last, channel=clock, kinds=["edges"])

for measure in workspace.measures.wait(timeout=60.0):
for result in measure.results:
print(f"{result.kind:12} {result.value}")
# time_total 0.02
# freq_avg 51480.17
# freq_min 19638.65
# freq_max 107758.62
# edges 101.0

workspace.close()

measures.between() places the markers for you. If you want to place them yourself, for instance so that they are visible when someone opens the .scana in ScanaStudio, use measures.add:

start = workspace.markers.add(sample=0)
stop = workspace.markers.add(sample=last)
workspace.measures.add(start, stop, channel=clock, kinds=["time", "frequency"])
Ask for edges, or ask for nothing

The server currently recognises two cases. A kinds list containing edges returns the edge count and nothing else. Any other list, or none, returns the standard timing set: time_total, freq_avg, freq_min, freq_max. The other kind names are accepted but do not change the result, and the kind names in a result are not the ones you asked for. Read result.kind rather than assuming. See Markers and measurements.

Example output. The two measurements above, over a 500 000-sample window:

time_total 0.02
freq_avg 51480.173999
freq_min 19638.648861
freq_max 107758.62069
edges 101.0

Every pulse, from the raw transitions​

When you need the distribution rather than a summary, read the edges and compute it yourself.

import statistics

from ikalogic_scanastudio import ScanaStudio


def main(device: str = "se254", channel: int = 0) -> int:
with ScanaStudio.connect() as server:
workspace = server.create(device)
workspace.capture.run(samples=500_000, sample_rate=25_000_000)

# Divide by the rate the DEVICE settled on, not the one we asked for.
rate = workspace.capture.sample_rate
edges = list(workspace.data.transitions(channel=int(channel)))

# A high pulse is the span from a rising edge to the next edge.
widths = [
(b.sample - a.sample) / rate
for a, b in zip(edges, edges[1:], strict=False)
if a.level == 1
]
if not widths:
print("no high pulses on that channel")
return 1

print(f"{len(widths)} high pulses on channel {channel}")
print(f" shortest {min(widths) * 1e9:.1f} ns")
print(f" longest {max(widths) * 1e9:.1f} ns")
print(f" median {statistics.median(widths) * 1e9:.1f} ns")

workspace.close()
return 0

Example output. The pulse-width distribution on a demo channel:

44 high pulses on channel 0
shortest 4320.0 ns
longest 63880.0 ns
median 12240.0 ns

The spread is wide because this is noise. On a real clock these three numbers sit close together, and the outliers are what you are looking for.

Notes for your own bench​

Divide by the rate you got back. capture.sample_rate is what the device settled on, which is not always what you asked for. Using the requested rate gives results that are consistently off by a few percent.

Skip the first edge in a width calculation if it predates your window. The first record from transitions() is the level in force where you started reading, so its sample index can be earlier than your start. Over a whole capture starting at 0 this does not matter; over a window it does.

Pick the approach by what you need. A pass/fail on "the clock is 8 MHz ± 1 %" is a measurement: read freq_avg. "Show me every pulse that was short", or anything needing a duty cycle or a distribution, needs the raw transitions. The server does not compute those today.

Measurements are annotations. They are saved into the .scana and reappear when someone opens it in ScanaStudio, which makes them a good way to leave a note about what your script found.

Anchored measurements follow their markers

Move a marker and the measurement is recomputed. Remove a marker and the measurement anchored to it goes too.