Skip to main content

Pattern generator

The pattern generator drives the device's outputs from a script, so the instrument stimulates the board as well as watching it. Reached through workspace.pattern.

Like a decoder, a generator is a JavaScript file in the server's script library, one implementing on_pattern_generate. See pattern generator scripts for writing one.

Which devices run a generator script

The scripted pattern generator on this page is an SP1000G-series feature (SP1018G, SP1036G, SP1054G). capabilities(...).pattern_generator says whether a device has it.

The SQ family (SQ25, SQ50, SQ100, SQ200) also generates signals, through generation rows and signal builders rather than a generator script. See generation and the operation capture option in capture. capabilities(...).signal_generator says whether a device has it.

The SE254, SP209 and SP259 families capture only.

The examples on this page use sp1018, the SP1018G demo device (hw:<serial> for the real thing). Its channels are group-prefixed (G1 PWM, G1 I2C SCL, G2 UART TX), one group per block of 18 channels.

preview() is interpreted by the server rather than sent to hardware, so it runs on any device. That makes it possible to write a generator without an SP1000G at hand, but it does not prove that the attached device could play it.

One generator per workspace

The device has a single pattern generator, so a workspace has one too. Adding a second replaces the first: there is no list and no instance id, just current.

options and add​

for option in workspace.pattern.options("pwm.js"):
print(option)

generator = workspace.pattern.add("pwm.js", {
"channel": workspace.channel("G1 PWM"),
"simple_freq_val": 1_000_000, # 1 MHz
"simple_pwm_val": 25, # 25 % duty
"nb_of_cycles": "100",
})
print(generator)

Example output. pwm.js on an SP1018G. Each option with its type, unit, tab and default:

channel: Target channel (ch_selector) = None
simple_freq_val [Fixed duty cycle]: Frequency (engineering_input) in Hz = nan
simple_pwm_val [Fixed duty cycle]: Duty cycle (engineering_input) in % = 50.0
mod_type [Modulated frequency]: Modulation type (combo) one of ['Sine', 'Triangle', 'SawTooth'] = 'Sine'
f_mod [Modulated frequency]: Modulation frequency (engineering_input) in Hz = nan
ph_mod [Modulated frequency]: Modulation phase (engineering_input) in DEG = 0.0
freq_carrier [Modulated frequency]: Carrier frequency (engineering_input) in Hz = nan
duty_min [Modulated frequency]: Carrier minimum (lower) duty cycle (engineering_input) in % = 10.0
duty_max [Modulated frequency]: Carrier maximum (upper) duty cycle (engineering_input) in % = 90.0
nb_of_cycles: Number of cycles (0 = infinite loop) (text_input) = '1000'

and the generator that add() returns:

instance_id=1 file_name='pwm.js' name='PWM Builder on CH 8' enabled=True pausable=False paused=False

A nan default means the script leaves it unset and expects you to supply it (simple_freq_val here). The bracketed name is the tab the option sits under in the ScanaStudio dialog.

add() returns the attached generator (PatternDesc), or None (null in NodeJS) if the server did not attach one. A script that refuses the values raises ScriptError.

Options work exactly as they do for decoders: discovered at runtime, keyed by id or caption, anything omitted keeps the script's default.

current​

The generator attached to this workspace, if any.

generator = workspace.pattern.current # PatternDesc, or None
generator.file_name
generator.name
generator.enabled
generator.pausable # the script can pause mid-generation
generator.paused

workspace.pattern.config() # its current settings, which update() takes

config() returns the attached generator's options with their current values as defaults, in the same form as options().

preview​

Runs the generator script through an interpreter instead of the device. The result replaces the workspace data exactly as an acquisition would. The script runs unchanged, so the preview shows the real program. This is how you develop and verify a generator with no hardware attached.

workspace.pattern.preview(timeout=300.0)

# The preview lands in the workspace like a capture would:
for edge in workspace.data.transitions(channel=workspace.channel("G1 PWM")):
print(edge.sample, edge.level)

If the script fails, preview raises ScriptError, whose logs hold what the script printed. In C, ss_pattern_preview returns a failure status and ss_last_error() holds the message.

Preview overwrites the capture

The preview replaces whatever the workspace was displaying. Save anything you still need first.

resume​

A generator script can pause its generation with builder_add_pause. resume continues a generation paused that way. It is the same call in every language.

if workspace.pattern.current and workspace.pattern.current.paused:
workspace.pattern.resume()

set_enabled and set_generate_on_trigger​

set_enabled turns generation off without losing the configuration.

set_generate_on_trigger decides when the pattern plays: on the acquisition's trigger, or as soon as the acquisition starts.

workspace.pattern.set_enabled(True)
workspace.pattern.set_generate_on_trigger(True)
workspace.pattern.generate_on_trigger # read it back

update and remove​

workspace.pattern.update({"simple_freq_val": 500_000}) # 500 kHz
workspace.pattern.remove()

An option the script does not have is refused before anything is sent: UnknownOption in Python (a subclass of KeyError), NodeJS and Rust, with the list of options the script takes.

stop_generation​

Generation does not end with the capture: the device keeps generating after the acquisition is done. It stops when the pattern ends, when the next capture starts, or when you call stop_generation.

workspace.pattern.stop_generation()

duration​

How long the attached generator's pattern lasts, loops included. The server runs the script against a counter, so nothing is generated. Use it to size the capture's samples so the acquisition covers the whole pattern.

The result has three fields:

FieldMeaning
secondsDuration of every chunk the script starts, loops included.
endlessA chunk never ends by itself: an endless loop, or a pause.
over_limitCounting stopped at limit_seconds: the pattern lasts at least seconds.
d = workspace.pattern.duration(limit_seconds=10.0)
# {'seconds': 0.0002, 'endless': False, 'over_limit': False}
if not d["endless"] and not d["over_limit"]:
samples = int(d["seconds"] * sample_rate) + 1

Without a limit, the server's default applies. A script that cannot be counted raises ScriptError.

Generating while capturing​

On real hardware the generator runs alongside the acquisition, and a script can produce its pattern in chunks as the capture goes.

The acquisition waits for the first chunk

The capture is only armed once the first chunk of the pattern has been loaded and started, so that the stimulus cannot arrive after the trigger. If the script generates nothing at all, the acquisition is refused with a Refused error.

The generator script's configuration is checked again when the capture starts. If the script refuses it, the capture is refused with Refused, and the refusal's subject is pattern_generator.

The full working pattern is on Stimulus and capture together.