diff --git a/scope.py b/scope.py index 3b275a9..02b0a2f 100755 --- a/scope.py +++ b/scope.py @@ -16,13 +16,16 @@ import vm LOG = logging.getLogger('scope') +class UsageError(Exception): + pass + class ConfigurationError(Exception): pass class Scope(vm.VirtualMachine): - AnalogParams = namedtuple('AnalogParams', ['rd', 'rr', 'rt', 'rb', 'scale', 'offset', 'safe_high', 'safe_low', 'ab_offset']) + AnalogParams = namedtuple('AnalogParams', ['la', 'lb', 'lc', 'ha', 'hb', 'hc', 'scale', 'offset', 'safe_low', 'safe_high', 'ab_offset']) @classmethod async def connect(cls, device=None): @@ -46,24 +49,23 @@ class Scope(vm.VirtualMachine): await self.issue_get_revision() revision = ((await self.read_replies(2))[1]).decode('ascii') if revision == 'BS000501': - self.capture_clock_period = 25e-9 + self.master_clock_period = 25e-9 self.capture_buffer_size = 12<<10 - self.awg_clock_period = self.capture_clock_period self.awg_wavetable_size = 1024 self.awg_sample_buffer_size = 1024 self.awg_minimum_clock = 33 - self.awg_maximum_voltage = 3.3 - self.analog_params = {'x1': self.AnalogParams(20.2, 300, 339, 352, 18.5, -7.59, 8, -5.5, 19e-3), - 'x10': self.AnalogParams(20.6, 302, 353, 351, 188, -92, 65.5, -70.9, 236e-3)} + self.logic_low = 0 + self.awg_maximum_voltage = self.clock_voltage = self.logic_high = 3.3 + self.analog_params = {'x1': self.AnalogParams(1.11, -6.57e-2, 8.46e-3, 1.11, -7.32e-2, -5.19e-2, 18.28, -7.45, -5.5, 8, 5.3e-3), + 'x10': self.AnalogParams(1.10, -6.11e-2, 8.61e-3, 1.10, -6.68e-2, -4.32e-2, 184.3, -90.4, -71.3, 65.7, 175e-3)} self.analog_lo_min = 0.07 self.analog_hi_max = 0.88 - self.logic_low = 0 - self.logic_high = 3.3 - self.timeout_clock_period = (1<<8) * self.capture_clock_period - self.timestamp_rollover = (1<<32) * self.capture_clock_period + self.timeout_clock_period = (1<<8) * self.master_clock_period + self.timestamp_rollover = (1<<32) * self.master_clock_period else: raise RuntimeError(f"Unsupported scope, revision: {revision}") self._awg_running = False + self._clock_running = False LOG.info(f"Initialised scope, revision: {revision}") def __enter__(self): @@ -78,11 +80,11 @@ class Scope(vm.VirtualMachine): def calculate_lo_hi(self, low, high, params): if not isinstance(params, self.AnalogParams): - params = self.AnalogParams(*(list(params) + [None]*(9-len(params)))) + params = self.AnalogParams(*(list(params) + [None]*(11-len(params)))) l = (low - params.offset) / params.scale h = (high - params.offset) / params.scale - dl = l - params.rd*(h-l)/params.rr + params.rd*l/params.rb - dh = h + params.rd*(h-l)/params.rr - params.rd*(1-h)/params.rt + dl = params.la*l + params.lb*h + params.lc + dh = params.ha*h + params.hb*l + params.hc return dl, dh async def capture(self, channels=['A'], trigger=None, trigger_level=None, trigger_type='rising', hair_trigger=False, @@ -108,6 +110,8 @@ class Scope(vm.VirtualMachine): raise ValueError(f"Unrecognised channel: {channel}") if self._awg_running and 4 in logic_channels: logic_channels.remove(4) + if self._clock_running and 5 in logic_channels: + logic_channels.remove(5) if 'A' in analog_channels and 7 in logic_channels: logic_channels.remove(7) if 'B' in analog_channels and 6 in logic_channels: @@ -115,25 +119,25 @@ class Scope(vm.VirtualMachine): analog_enable = sum(1<<(ord(channel)-ord('A')) for channel in analog_channels) logic_enable = sum(1< capture_mode.clock_high and capture_mode.clock_divide: for clock_scale in range(2, vm.Registers.ClockScale.maximum_value+1): - test_ticks = int(round(period / nsamples / self.capture_clock_period / clock_scale)) + test_ticks = int(round(period / nsamples / self.master_clock_period / clock_scale)) if test_ticks in range(capture_mode.clock_low, capture_mode.clock_high + 1): ticks = test_ticks break else: continue break - elif ticks > capture_mode.clock_low: + elif ticks >= capture_mode.clock_low: clock_scale = 1 if ticks > capture_mode.clock_high: ticks = capture_mode.clock_high else: continue - n = int(round(period / ticks / self.capture_clock_period / clock_scale)) + n = int(round(period / ticks / self.master_clock_period / clock_scale)) if len(analog_channels) == 2: n -= n % 2 buffer_width = self.capture_buffer_size // capture_mode.sample_width @@ -207,7 +211,7 @@ class Scope(vm.VirtualMachine): if timeout is None: trigger_timeout = 0 else: - trigger_timeout = int(math.ceil(((trigger_intro+trigger_outro+trace_outro+2)*ticks*clock_scale*self.capture_clock_period + trigger_timeout = int(math.ceil(((trigger_intro+trigger_outro+trace_outro+2)*ticks*clock_scale*self.master_clock_period + timeout)/self.timeout_clock_period)) if trigger_timeout > vm.Registers.Timeout.maximum_value: if timeout > 0: @@ -215,7 +219,7 @@ class Scope(vm.VirtualMachine): else: raise ConfigurationError("Required trigger timeout too long, use a later trigger position") - sample_period = ticks*clock_scale*self.capture_clock_period + sample_period = ticks*clock_scale*self.master_clock_period sample_rate = 1/sample_period LOG.info(f"Begin {('mixed' if logic_channels else 'analogue') if analog_channels else 'logic'} signal capture " f"at {sample_rate:,.0f} samples per second (trace mode {capture_mode.trace_mode.name})") @@ -248,8 +252,8 @@ class Scope(vm.VirtualMachine): address -= address % 2 traces = vm.DotDict() - timestamps = array.array('d', (t*self.capture_clock_period for t in range(start_timestamp, timestamp, ticks*clock_scale))) - start_time = start_timestamp*self.capture_clock_period + timestamps = array.array('d', (t*self.master_clock_period for t in range(start_timestamp, timestamp, ticks*clock_scale))) + start_time = start_timestamp*self.master_clock_period for dump_channel, channel in enumerate(sorted(analog_channels)): asamples = nsamples // len(analog_channels) async with self.transaction(): @@ -261,7 +265,7 @@ class Scope(vm.VirtualMachine): value_multiplier, value_offset = (1, 0) if raw else (high-low, low-analog_params.ab_offset/2*(1 if channel == 'A' else -1)) data = await self.read_analog_samples(asamples, capture_mode.sample_width) traces[channel] = vm.DotDict({'timestamps': timestamps[dump_channel::len(analog_channels)] if len(analog_channels) > 1 else timestamps, - 'samples': array.array('d', (value*value_multiplier+value_offset for value in data)), + 'samples': array.array('f', (value*value_multiplier+value_offset for value in data)), 'start_time': start_time+sample_period*dump_channel, 'sample_period': sample_period*len(analog_channels), 'sample_rate': sample_rate/len(analog_channels), @@ -284,23 +288,24 @@ class Scope(vm.VirtualMachine): LOG.info(f"{nsamples} samples captured on {cause}, traces: {', '.join(traces)}") return traces - async def start_generator(self, frequency, waveform='sine', wavetable=None, ratio=0.5, - low=0, high=None, min_samples=50, max_error=1e-4): + async def start_waveform(self, frequency, waveform='sine', ratio=0.5, low=0, high=None, min_samples=50, max_error=1e-4): + if self._clock_running: + raise UsageError("Cannot start waveform generator while clock in use") if high is None: high = self.awg_maximum_voltage elif high < 0 or high > self.awg_maximum_voltage: raise ValueError(f"high out of range (0-{self.awg_maximum_voltage})") if low < 0 or low > high: - raise ValueError("offset out of range (0-high)") + raise ValueError("low out of range (0-high)") possible_params = [] - max_clock = int(math.floor(1 / frequency / min_samples / self.awg_clock_period)) + max_clock = int(math.floor(1 / frequency / min_samples / self.master_clock_period)) for clock in range(self.awg_minimum_clock, max_clock+1): - width = 1 / frequency / (clock * self.awg_clock_period) + width = 1 / frequency / (clock * self.master_clock_period) if width <= self.awg_sample_buffer_size: nwaves = int(self.awg_sample_buffer_size / width) size = int(round(nwaves * width)) width = size / nwaves - actualf = 1 / (width * clock * self.awg_clock_period) + actualf = 1 / (width * clock * self.master_clock_period) error = abs(frequency - actualf) / frequency if error < max_error: possible_params.append((width if error == 0 else -error, (size, nwaves, clock, actualf))) @@ -308,16 +313,16 @@ class Scope(vm.VirtualMachine): raise ConfigurationError("No solution to required frequency/min_samples/max_error") size, nwaves, clock, actualf = sorted(possible_params)[-1][1] async with self.transaction(): - if wavetable is None: + if isinstance(waveform, str): mode = {'sine': 0, 'triangle': 1, 'exponential': 2, 'square': 3}[waveform.lower()] await self.set_registers(Cmd=0, Mode=mode, Ratio=ratio) await self.issue_synthesize_wavetable() - else: - if len(wavetable) != self.awg_wavetable_size: - raise ValueError(f"Wavetable data must be {self.awg_wavetable_size} samples") + elif len(wavetable) == self.awg_wavetable_size: wavetable = bytes(min(max(0, int(round(y*255))),255) for y in wavetable) await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1) await self.wavetable_write_bytes(wavetable) + else: + raise ValueError(f"waveform must be a valid name or {self.awg_wavetable_size} samples") async with self.transaction(): offset = (high+low)/2 - self.awg_maximum_voltage/2 await self.set_registers(Cmd=0, Mode=0, Level=(high-low)/self.awg_maximum_voltage, @@ -328,7 +333,7 @@ class Scope(vm.VirtualMachine): async with self.transaction(): await self.set_registers(Cmd=2, Mode=0, Clock=clock, Modulo=size, Mark=10, Space=1, Rest=0x7f00, Option=0x8004) - await self.issue_control_waveform_generator() + await self.issue_control_clock_generator() async with self.transaction(): await self.set_registers(KitchenSinkB=vm.KitchenSinkB.WaveformGeneratorEnable) await self.issue_configure_device_hardware() @@ -336,15 +341,41 @@ class Scope(vm.VirtualMachine): LOG.info(f"Signal generator running at {actualf:0.1f}Hz") return actualf - async def stop_generator(self): + async def stop_waveform(self): + if not self._awg_running: + raise UsageError("Waveform generator not in use") async with self.transaction(): await self.set_registers(Cmd=1, Mode=0) - await self.issue_control_waveform_generator() + await self.issue_control_clock_generator() await self.set_registers(KitchenSinkB=0) await self.issue_configure_device_hardware() LOG.info("Signal generator stopped") self._awg_running = False + async def start_clock(self, frequency, ratio=0.5, max_error=1e-4): + if self._awg_running: + raise UsageError("Cannot start clock while waveform generator in use") + ticks = min(max(2, int(round(1 / frequency / self.master_clock_period))), vm.Registers.Clock.maximum_value) + fall = min(max(1, int(round(ticks * ratio))), ticks-1) + actualf, actualr = 1 / ticks / self.master_clock_period, fall / ticks + if abs(actualf - frequency) / frequency > max_error: + raise ConfigurationError("No solution to required frequency and max_error") + async with self.transaction(): + await self.set_registers(Map5=0x12, Clock=ticks, Rise=0, Fall=fall, Control=0x80, Cmd=3, Mode=0) + await self.issue_control_clock_generator() + self._clock_running = True + LOG.info(f"Clock generator running at {actualf:0.1f}Hz, {actualr*100:.0f}% duty cycle") + return actualf, actualr + + async def stop_clock(self): + if not self._clock_running: + raise UsageError("Clock not in use") + async with self.transaction(): + await self.set_registers(Map5=0, Cmd=1, Mode=0) + await self.issue_control_clock_generator() + LOG.info("Clock generator stopped") + self._clock_running = False + async def read_wavetable(self): with self.transaction(): self.set_registers(Address=0, Size=self.awg_wavetable_size) @@ -366,59 +397,66 @@ class Scope(vm.VirtualMachine): async def calibrate(self, probes='x1', n=32): import numpy as np - from scipy.optimize import least_squares, minimize + from scipy.optimize import minimize items = [] - await self.start_generator(frequency=1000, waveform='square') + async def measure(lo, hi, period=2e-3, chop=True): + if chop: + traces = await self.capture(channels=['A','B'], period=period, nsamples=2000, timeout=0, low=lo, high=hi, raw=True) + A = np.array(traces.A.samples) + B = np.array(traces.B.samples) + else: + A = np.array((await self.capture(channels=['A'], period=period/2, nsamples=1000, timeout=0, low=lo, high=hi, raw=True)).A.samples) + B = np.array((await self.capture(channels=['B'], period=period/2, nsamples=1000, timeout=0, low=lo, high=hi, raw=True)).B.samples) + Amean = A.mean() + Azero, Afull = np.median(A[A<=Amean]), np.median(A[A>=Amean]) + Bmean = B.mean() + Bzero, Bfull = np.median(B[B<=Bmean]), np.median(B[B>=Bmean]) + return (Azero + Bzero) / 2, (Afull + Bfull) / 2, ((Afull - Bfull) + (Azero - Azero)) / 2 + await self.start_clock(frequency=2000) + zero, full, offset = await measure(1/3, 2/3) + zero = (zero + 1) / 3 + full = (full + 1) / 3 + analog_scale = self.clock_voltage / (full - zero) + analog_offset = -zero * analog_scale + LOG.info(f"Analog full range = {analog_scale:.1f}V, zero offset = {analog_offset:.1f}V") for lo in np.linspace(self.analog_lo_min, 0.5, n, endpoint=False): for hi in np.linspace(self.analog_hi_max, 0.5, n): - if len(items) % 2 == 0: - traces = await self.capture(channels=['A','B'], period=2e-3, nsamples=2000, timeout=0, low=lo, high=hi, raw=True) - A = np.array(traces.A.samples) - B = np.array(traces.B.samples) - else: - A = np.array((await self.capture(channels=['A'], period=2e-3, nsamples=1000, timeout=0, low=lo, high=hi, raw=True)).A.samples) - B = np.array((await self.capture(channels=['B'], period=2e-3, nsamples=1000, timeout=0, low=lo, high=hi, raw=True)).B.samples) - A.sort() - Azero, Amax = A[25:475].mean(), A[525:975].mean() - if Azero < 0.01 or Amax > 0.99: - continue - B.sort() - Bzero, Bmax = B[25:475].mean(), B[525:975].mean() - if Bzero < 0.01 or Bmax > 0.99: - continue - zero = (Azero + Bzero) / 2 - analog_range = self.awg_maximum_voltage / ((Amax + Bmax)/2 - zero) - low = -zero * analog_range - high = low + analog_range - offset = ((Amax - Bmax) + (Azero - Bzero))/2 * analog_range - items.append((lo, hi, low, high, offset)) - await self.stop_generator() + period = 2e-3 if len(items) % 4 < 2 else 1e-3 + zero, full, offset = await measure(lo, hi, 2e-3 if len(items) % 4 < 2 else 1e-3, len(items) % 2 == 0) + if zero > 0.01 and full < 0.99 and full > zero: + analog_range = self.clock_voltage / (full - zero) + items.append((lo, hi, -zero*analog_range, (1-zero)*analog_range, offset*analog_range)) + await self.stop_clock() items = np.array(items).T - def f(params, lo, hi, low, high, offset): - clo, chi = self.calculate_lo_hi(low, high, params) - return np.sqrt((lo-clo)**2 + (hi-chi)**2) - start_params = self.analog_params.get(probes, self.AnalogParams(20, 300, 300, 300, 18.5, -7.585, None, None, None))[:-3] - result = least_squares(f, start_params, args=items, bounds=([10, 200, 200, 200, 1, -500], [30, 400, 400, 400, 1000, 0])) + lo, hi, low, high, offset = items + def f(params): + dl, dh = self.calculate_lo_hi(low, high, self.AnalogParams(*params, analog_scale, analog_offset, None, None, None)) + return np.sqrt((lo-dl)**2 + (hi-dh)**2).mean() + start_params = self.analog_params.get(probes, [1,0,0,1,0,0])[:6] + result = minimize(f, start_params, method='SLSQP', + bounds=[(1,np.inf), (-np.inf,0), (0,np.inf), (1,np.inf), (-np.inf,0), (-np.inf,0)], + constraints=[{'type': 'eq', 'fun': lambda x: x[0]*1/3 + x[1]*2/3 + x[2] - 1/3}, + {'type': 'eq', 'fun': lambda x: x[3]*2/3 + x[4]*1/3 + x[5] - 2/3}]) if result.success: LOG.info(f"Calibration succeeded: {result.message}") - lo, hi, low, high, offset = items - offset_mean = offset.mean() - LOG.info(f"Mean A-B offset: {offset_mean*1000:.1f}mV (+/- {100*offset.std()/offset_mean:.1f}%)") + params = self.AnalogParams(*result.x, analog_scale, analog_offset, None, None, None) def f(x): - lo, hi = self.calculate_lo_hi(x[0], x[1], result.x) + lo, hi = self.calculate_lo_hi(x[0], x[1], params) return np.sqrt((self.analog_lo_min - lo)**2 + (self.analog_hi_max - hi)**2) - safe_low, safe_high = minimize(f, (low[0],high[0])).x - params = self.analog_params[probes] = self.AnalogParams(*result.x, safe_high, safe_low, offset_mean) - LOG.info(f"Analog parameters: rd={params.rd:.1f}Ω rr={params.rr:.1f}Ω rt={params.rt:.1f}Ω rb={params.rb:.1f}Ω " + safe_low, safe_high = minimize(f, (low[0], high[0])).x + offset_mean = offset.mean() + params = self.analog_params[probes] = self.AnalogParams(*result.x, analog_scale, analog_offset, safe_low, safe_high, offset_mean) + LOG.info(f"Analog parameters: la={params.la:.3e} lb={params.lb:.3e} lc={params.lc:.3e} " + f"ha={params.ha:.3e} hb={params.hb:.3e} hc={params.hc:.3e} " f"scale={params.scale:.3f}V offset={params.offset:.3f}V " - f"safe_high={params.safe_high:.1f}V safe_low={params.safe_low:.1f}V") + f"safe_low={params.safe_low:.1f}V safe_high={params.safe_high:.1f}V " + f"ab_offset={offset_mean*1000:.1f}mV (±{100*offset.std()/offset_mean:.1f}%)") clo, chi = self.calculate_lo_hi(low, high, params) lo_error = np.sqrt((((clo-lo)/(hi-lo))**2).mean()) hi_error = np.sqrt((((chi-hi)/(hi-lo))**2).mean()) LOG.info(f"Mean error: lo={lo_error*10000:.1f}bps hi={hi_error*10000:.1f}bps") else: LOG.warning(f"Calibration failed: {result.message}") - return items return result.success @@ -454,7 +492,7 @@ async def main(): logging.basicConfig(level=logging.DEBUG if args.debug else (logging.INFO if args.verbose else logging.WARNING), stream=sys.stdout) s = await Scope.connect(args.device) -def await(g): +def await_(g): task = asyncio.Task(g) while True: try: @@ -463,7 +501,7 @@ def await(g): task.cancel() def capture(*args, **kwargs): - return await(s.capture(*args, **kwargs)) + return await_(s.capture(*args, **kwargs)) def capturep(*args, **kwargs): import pandas @@ -471,10 +509,13 @@ def capturep(*args, **kwargs): return pandas.DataFrame({channel: pandas.Series(trace.samples, trace.timestamps) for (channel,trace) in traces.items()}) def calibrate(*args, **kwargs): - return await(s.calibrate(*args, **kwargs)) + return await_(s.calibrate(*args, **kwargs)) -def generate(*args, **kwargs): - return await(s.start_generator(*args, **kwargs)) +def start_waveform(*args, **kwargs): + return await_(s.start_waveform(*args, **kwargs)) + +def start_clock(*args, **kwargs): + return await_(s.start_clock(*args, **kwargs)) if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main()) diff --git a/vm.py b/vm.py index d292cd5..7454c8e 100644 --- a/vm.py +++ b/vm.py @@ -216,27 +216,27 @@ class TraceStatus(IntEnum): Wait = 0x02 Stop = 0x03 -CaptureMode = namedtuple('CaptureMode', ('clock_low', 'clock_high', 'analog_channels', 'sample_width', - 'logic_channels', 'clock_divide', 'trace_mode', 'buffer_mode')) +CaptureMode = namedtuple('CaptureMode', ('trace_mode', 'clock_low', 'clock_high', 'clock_divide', + 'analog_channels', 'sample_width', 'logic_channels', 'buffer_mode')) CaptureModes = [ - CaptureMode(40, 16384, 1, 2, False, False, TraceMode.Macro, BufferMode.Macro), - CaptureMode(40, 16384, 2, 2, False, False, TraceMode.MacroChop, BufferMode.MacroChop), - CaptureMode(15, 40, 1, 1, False, True, TraceMode.Analog, BufferMode.Single), - CaptureMode(13, 40, 2, 1, False, True, TraceMode.AnalogChop, BufferMode.Chop), - CaptureMode( 8, 14, 1, 1, False, False, TraceMode.AnalogFast, BufferMode.Single), - CaptureMode( 8, 40, 2, 1, False, False, TraceMode.AnalogFastChop, BufferMode.Chop), - CaptureMode( 2, 5, 1, 1, False, False, TraceMode.AnalogShot, BufferMode.Single), - CaptureMode( 4, 5, 2, 1, False, False, TraceMode.AnalogShotChop, BufferMode.Chop), - CaptureMode( 5, 16384, 0, 1, True, False, TraceMode.Logic, BufferMode.Single), - CaptureMode( 4, 4, 0, 1, True, False, TraceMode.LogicFast, BufferMode.Single), - CaptureMode( 1, 3, 0, 1, True, False, TraceMode.LogicShot, BufferMode.Single), - CaptureMode(15, 40, 1, 1, True, True, TraceMode.Mixed, BufferMode.Dual), - CaptureMode(13, 40, 2, 1, True, True, TraceMode.MixedChop, BufferMode.ChopDual), - CaptureMode( 8, 14, 1, 1, True, False, TraceMode.MixedFast, BufferMode.Dual), - CaptureMode( 8, 40, 2, 1, True, False, TraceMode.MixedFastChop, BufferMode.ChopDual), - CaptureMode( 2, 5, 1, 1, True, False, TraceMode.MixedShot, BufferMode.Dual), - CaptureMode( 4, 5, 2, 1, True, False, TraceMode.MixedShotChop, BufferMode.ChopDual), + CaptureMode(TraceMode.Macro, 40, 16384, False, 1, 2, False, BufferMode.Macro), + CaptureMode(TraceMode.MacroChop, 40, 16384, False, 2, 2, False, BufferMode.MacroChop), + CaptureMode(TraceMode.Analog, 15, 40, True, 1, 1, False, BufferMode.Single), + CaptureMode(TraceMode.AnalogChop, 13, 40, True, 2, 1, False, BufferMode.Chop), + CaptureMode(TraceMode.AnalogFast, 8, 14, False, 1, 1, False, BufferMode.Single), + CaptureMode(TraceMode.AnalogFastChop, 8, 40, False, 2, 1, False, BufferMode.Chop), + CaptureMode(TraceMode.AnalogShot, 2, 5, False, 1, 1, False, BufferMode.Single), + CaptureMode(TraceMode.AnalogShotChop, 4, 5, False, 2, 1, False, BufferMode.Chop), + CaptureMode(TraceMode.Logic, 5, 16384, False, 0, 1, True, BufferMode.Single), + CaptureMode(TraceMode.LogicFast, 4, 4, False, 0, 1, True, BufferMode.Single), + CaptureMode(TraceMode.LogicShot, 1, 3, False, 0, 1, True, BufferMode.Single), + CaptureMode(TraceMode.Mixed, 15, 40, True, 1, 1, True, BufferMode.Dual), + CaptureMode(TraceMode.MixedChop, 13, 40, True, 2, 1, True, BufferMode.ChopDual), + CaptureMode(TraceMode.MixedFast, 8, 14, False, 1, 1, True, BufferMode.Dual), + CaptureMode(TraceMode.MixedFastChop, 8, 40, False, 2, 1, True, BufferMode.ChopDual), + CaptureMode(TraceMode.MixedShot, 2, 5, False, 1, 1, True, BufferMode.Dual), + CaptureMode(TraceMode.MixedShotChop, 4, 5, False, 2, 1, True, BufferMode.ChopDual), ] @@ -421,7 +421,7 @@ class VirtualMachine: async def issue_translate_wavetable(self): await self.issue(b'X') - async def issue_control_waveform_generator(self): + async def issue_control_clock_generator(self): await self.issue(b'Z') async def issue_read_eeprom(self):