From b215df2d25b69082802f596d28271e4f0ee0d99d Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Mon, 8 Jan 2024 14:42:28 +0800 Subject: [PATCH] comm_analyzer: add WaveformManager, WaveformChannel --- artiq/coredevice/comm_analyzer.py | 192 +++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 56 deletions(-) diff --git a/artiq/coredevice/comm_analyzer.py b/artiq/coredevice/comm_analyzer.py index 09ad3618f..3e3fcafba 100644 --- a/artiq/coredevice/comm_analyzer.py +++ b/artiq/coredevice/comm_analyzer.py @@ -34,6 +34,13 @@ class ExceptionType(Enum): i_overflow = 0b100001 +class WaveformType(Enum): + ANALOG = 0 + BIT = 1 + VECTOR = 2 + LOG = 3 + + def get_analyzer_dump(host, port=1382): sock = socket.create_connection((host, port)) try: @@ -150,6 +157,12 @@ class VCDChannel: integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0] self.set_value("{:064b}".format(integer_cast)) + def set_log(self, log_message): + value = "" + for c in log_message: + value += "{:08b}".format(ord(c)) + self.set_value(value) + class VCDManager: def __init__(self, fileobj): @@ -160,15 +173,15 @@ class VCDManager: def set_timescale_ps(self, timescale): self.out.write("$timescale {}ps $end\n".format(round(timescale))) - def get_channel(self, name, width): + def get_channel(self, name, width, ty): code = next(self.codes) self.out.write("$var wire {width} {code} {name} $end\n" .format(name=name, code=code, width=width)) return VCDChannel(self.out, code) @contextmanager - def scope(self, name): - self.out.write("$scope module {} $end\n".format(name)) + def scope(self, scope, name): + self.out.write("$scope module {}/{} $end\n".format(scope, name)) yield self.out.write("$upscope $end\n") @@ -177,11 +190,66 @@ class VCDManager: self.out.write("#{}\n".format(time)) self.current_time = time + def set_end_time(self, time): + pass + + +class WaveformManager: + def __init__(self): + self.current_time = 0 + self.channels = list() + self.current_scope = "" + self.trace = {"timescale": 1, "stopped_x": None, "logs": dict(), "data": dict()} + + def set_timescale_ps(self, timescale): + self.trace["timescale"] = int(timescale) + + def get_channel(self, name, width, ty): + if ty == WaveformType.LOG: + data = self.trace["logs"][self.current_scope + name] = list() + else: + data = self.trace["data"][self.current_scope + name] = list() + channel = WaveformChannel(data, self.current_time) + self.channels.append(channel) + return channel + + @contextmanager + def scope(self, scope, name): + old_scope = self.current_scope + self.current_scope = scope + "/" + yield + self.current_scope = old_scope + + def set_time(self, time): + for channel in self.channels: + channel.set_time(time) + + def set_end_time(self, time): + self.trace["stopped_x"] = time + + +class WaveformChannel: + def __init__(self, data, current_time): + self.data = data + self.current_time = current_time + + def set_value(self, value): + self.data.append((self.current_time, value)) + + def set_value_double(self, x): + self.data.append((self.current_time, x)) + + def set_time(self, time): + self.current_time = time + + def set_log(self, log_message): + self.data.append((self.current_time, log_message)) + class TTLHandler: - def __init__(self, vcd_manager, name): + def __init__(self, manager, name): self.name = name - self.channel_value = vcd_manager.get_channel("ttl/" + name, 1) + self.channel_value = manager.get_channel("ttl/" + name, 1, ty=WaveformType.BIT) self.last_value = "X" self.oe = True @@ -206,11 +274,11 @@ class TTLHandler: class TTLClockGenHandler: - def __init__(self, vcd_manager, name, ref_period): + def __init__(self, manager, name, ref_period): self.name = name self.ref_period = ref_period - self.channel_frequency = vcd_manager.get_channel( - "ttl_clkgen/" + name, 64) + self.channel_frequency = manager.get_channel( + "ttl_clkgen/" + name, 64, ty=WaveformType.ANALOG) def process_message(self, message): if isinstance(message, OutputMessage): @@ -221,8 +289,8 @@ class TTLClockGenHandler: class DDSHandler: - def __init__(self, vcd_manager, onehot_sel, sysclk): - self.vcd_manager = vcd_manager + def __init__(self, manager, onehot_sel, sysclk): + self.manager = manager self.onehot_sel = onehot_sel self.sysclk = sysclk @@ -231,11 +299,11 @@ class DDSHandler: def add_dds_channel(self, name, dds_channel_nr): dds_channel = dict() - with self.vcd_manager.scope("dds/{}".format(name)): + with self.manager.scope("dds", name): dds_channel["vcd_frequency"] = \ - self.vcd_manager.get_channel(name + "/frequency", 64) + self.manager.get_channel(name + "/frequency", 64, ty=WaveformType.ANALOG) dds_channel["vcd_phase"] = \ - self.vcd_manager.get_channel(name + "/phase", 64) + self.manager.get_channel(name + "/phase", 64, ty=WaveformType.ANALOG) dds_channel["ftw"] = [None, None] dds_channel["pow"] = None self.dds_channels[dds_channel_nr] = dds_channel @@ -285,10 +353,10 @@ class DDSHandler: class WishboneHandler: - def __init__(self, vcd_manager, name, read_bit): + def __init__(self, manager, name, read_bit): self._reads = [] self._read_bit = read_bit - self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1) + self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT) def process_message(self, message): self.stb.set_value("1") @@ -318,16 +386,17 @@ class WishboneHandler: class SPIMasterHandler(WishboneHandler): - def __init__(self, vcd_manager, name): + def __init__(self, manager, name): self.channels = {} - with vcd_manager.scope("spi/{}".format(name)): - super().__init__(vcd_manager, name, read_bit=0b100) + self.scope = "spi" + with manager.scope("spi", name): + super().__init__(manager, name, read_bit=0b100) for reg_name, reg_width in [ ("config", 32), ("chip_select", 16), ("write_length", 8), ("read_length", 8), ("write", 32), ("read", 32)]: - self.channels[reg_name] = vcd_manager.get_channel( - "{}/{}".format(name, reg_name), reg_width) + self.channels[reg_name] = manager.get_channel( + "{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR) def process_write(self, address, data): if address == 0: @@ -352,11 +421,12 @@ class SPIMasterHandler(WishboneHandler): class SPIMaster2Handler(WishboneHandler): - def __init__(self, vcd_manager, name): + def __init__(self, manager, name): self._reads = [] self.channels = {} - with vcd_manager.scope("spi2/{}".format(name)): - self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1) + self.scope = "spi2" + with manager.scope("spi2", name): + self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT) for reg_name, reg_width in [ ("flags", 8), ("length", 5), @@ -364,8 +434,8 @@ class SPIMaster2Handler(WishboneHandler): ("chip_select", 8), ("write", 32), ("read", 32)]: - self.channels[reg_name] = vcd_manager.get_channel( - "{}/{}".format(name, reg_name), reg_width) + self.channels[reg_name] = manager.get_channel( + "{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR) def process_message(self, message): self.stb.set_value("1") @@ -413,11 +483,12 @@ def _extract_log_chars(data): class LogHandler: - def __init__(self, vcd_manager, vcd_log_channels): - self.vcd_channels = dict() - for name, maxlength in vcd_log_channels.items(): - self.vcd_channels[name] = vcd_manager.get_channel("log/" + name, - maxlength*8) + def __init__(self, manager, log_channels): + self.channels = dict() + for name, maxlength in log_channels.items(): + self.channels[name] = manager.get_channel("logs/" + name, + maxlength * 8, + ty=WaveformType.LOG) self.current_entry = "" def process_message(self, message): @@ -425,15 +496,12 @@ class LogHandler: self.current_entry += _extract_log_chars(message.data) if len(self.current_entry) > 1 and self.current_entry[-1] == "\x1D": channel_name, log_message = self.current_entry[:-1].split("\x1E", maxsplit=1) - vcd_value = "" - for c in log_message: - vcd_value += "{:08b}".format(ord(c)) - self.vcd_channels[channel_name].set_value(vcd_value) + self.channels[channel_name].set_log(log_message) self.current_entry = "" -def get_vcd_log_channels(log_channel, messages): - vcd_log_channels = dict() +def get_log_channels(log_channel, messages): + log_channels = dict() log_entry = "" for message in messages: if (isinstance(message, OutputMessage) @@ -442,13 +510,13 @@ def get_vcd_log_channels(log_channel, messages): if len(log_entry) > 1 and log_entry[-1] == "\x1D": channel_name, log_message = log_entry[:-1].split("\x1E", maxsplit=1) l = len(log_message) - if channel_name in vcd_log_channels: - if vcd_log_channels[channel_name] < l: - vcd_log_channels[channel_name] = l + if channel_name in log_channels: + if log_channels[channel_name] < l: + log_channels[channel_name] = l else: - vcd_log_channels[channel_name] = l + log_channels[channel_name] = l log_entry = "" - return vcd_log_channels + return log_channels def get_single_device_argument(devices, module, cls, argument): @@ -475,7 +543,7 @@ def get_dds_sysclk(devices): ("AD9914",), "sysclk") -def create_channel_handlers(vcd_manager, devices, ref_period, +def create_channel_handlers(manager, devices, ref_period, dds_sysclk, dds_onehot_sel): channel_handlers = dict() for name, desc in sorted(devices.items(), key=itemgetter(0)): @@ -483,11 +551,11 @@ def create_channel_handlers(vcd_manager, devices, ref_period, if (desc["module"] == "artiq.coredevice.ttl" and desc["class"] in {"TTLOut", "TTLInOut"}): channel = desc["arguments"]["channel"] - channel_handlers[channel] = TTLHandler(vcd_manager, name) + channel_handlers[channel] = TTLHandler(manager, name) if (desc["module"] == "artiq.coredevice.ttl" and desc["class"] == "TTLClockGen"): channel = desc["arguments"]["channel"] - channel_handlers[channel] = TTLClockGenHandler(vcd_manager, name, ref_period) + channel_handlers[channel] = TTLClockGenHandler(manager, name, ref_period) if (desc["module"] == "artiq.coredevice.ad9914" and desc["class"] == "AD9914"): dds_bus_channel = desc["arguments"]["bus_channel"] @@ -495,14 +563,14 @@ def create_channel_handlers(vcd_manager, devices, ref_period, if dds_bus_channel in channel_handlers: dds_handler = channel_handlers[dds_bus_channel] else: - dds_handler = DDSHandler(vcd_manager, dds_onehot_sel, dds_sysclk) + dds_handler = DDSHandler(manager, dds_onehot_sel, dds_sysclk) channel_handlers[dds_bus_channel] = dds_handler dds_handler.add_dds_channel(name, dds_channel) if (desc["module"] == "artiq.coredevice.spi2" and desc["class"] == "SPIMaster"): channel = desc["arguments"]["channel"] channel_handlers[channel] = SPIMaster2Handler( - vcd_manager, name) + manager, name) return channel_handlers @@ -512,11 +580,21 @@ def get_message_time(message): def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False): vcd_manager = VCDManager(fileobj) + decoded_dump_to_target(vcd_manager, devices, dump, uniform_interval) + + +def decoded_dump_to_waveform_data(devices, dump, uniform_interval=False): + manager = WaveformManager() + decoded_dump_to_target(manager, devices, dump, uniform_interval) + return manager.trace + + +def decoded_dump_to_target(manager, devices, dump, uniform_interval): ref_period = get_ref_period(devices) if ref_period is not None: if not uniform_interval: - vcd_manager.set_timescale_ps(ref_period*1e12) + manager.set_timescale_ps(ref_period*1e12) else: logger.warning("unable to determine core device ref_period") ref_period = 1e-9 # guess @@ -526,6 +604,8 @@ def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False): dds_sysclk = 3e9 # guess if isinstance(dump.messages[-1], StoppedMessage): + m = dump.messages[-1] + end_time = get_message_time(m) messages = dump.messages[:-1] else: logger.warning("StoppedMessage missing") @@ -533,20 +613,20 @@ def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False): messages = sorted(messages, key=get_message_time) channel_handlers = create_channel_handlers( - vcd_manager, devices, ref_period, + manager, devices, ref_period, dds_sysclk, dump.dds_onehot_sel) - vcd_log_channels = get_vcd_log_channels(dump.log_channel, messages) + log_channels = get_log_channels(dump.log_channel, messages) channel_handlers[dump.log_channel] = LogHandler( - vcd_manager, vcd_log_channels) + manager, log_channels) if uniform_interval: # RTIO event timestamp in machine units - timestamp = vcd_manager.get_channel("timestamp", 64) + timestamp = manager.get_channel("timestamp", 64, ty=WaveformType.VECTOR) # RTIO time interval between this and the next timed event # in SI seconds - interval = vcd_manager.get_channel("interval", 64) - slack = vcd_manager.get_channel("rtio_slack", 64) + interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG) + slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG) - vcd_manager.set_time(0) + manager.set_time(0) start_time = 0 for m in messages: start_time = get_message_time(m) @@ -560,11 +640,11 @@ def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False): if t >= 0: if uniform_interval: interval.set_value_double((t - t0)*ref_period) - vcd_manager.set_time(i) + manager.set_time(i) timestamp.set_value("{:064b}".format(t)) t0 = t else: - vcd_manager.set_time(t) + manager.set_time(t) channel_handlers[message.channel].process_message(message) if isinstance(message, OutputMessage): slack.set_value_double(