artiq/artiq/coredevice/analyzer.py

313 lines
12 KiB
Python
Raw Normal View History

2015-12-20 19:32:52 +08:00
from operator import itemgetter
2015-12-20 14:34:16 +08:00
from collections import namedtuple
2015-12-20 19:32:52 +08:00
from itertools import count
from enum import Enum
2015-12-20 14:34:16 +08:00
import struct
import logging
logger = logging.getLogger(__name__)
class MessageType(Enum):
output = 0b00
input = 0b01
exception = 0b10
class ExceptionType(Enum):
reset_rising = 0b000000
reset_falling = 0b000001
reset_phy_rising = 0b000010
reset_phy_falling = 0b000011
o_underflow_reset = 0b010000
o_sequence_error_reset = 0b010001
o_collision_error_reset = 0b010010
i_overflow_reset = 0b100000
OutputMessage = namedtuple(
"OutputMessage", "channel timestamp rtio_counter address data")
InputMessage = namedtuple(
"InputMessage", "channel timestamp rtio_counter data")
ExceptionMessage = namedtuple(
"ExceptionMessage", "channel rtio_counter exception_type")
def decode_message(data):
message_type_channel = struct.unpack(">I", data[28:32])[0]
message_type = MessageType(message_type_channel & 0b11)
channel = message_type_channel >> 2
if message_type == MessageType.output:
parts = struct.unpack(">QIQQ", data[:28])
data, address, rtio_counter, timestamp = parts
return OutputMessage(channel, timestamp, rtio_counter, address, data)
elif message_type == MessageType.input:
parts = struct.unpack(">QIQQ", data[:28])
data, _, rtio_counter, timestamp = parts
return InputMessage(channel, timestamp, rtio_counter, data)
elif message_type == MessageType.exception:
exception_type, rtio_counter = struct.unpack(">BQ", data[11:20])
return ExceptionMessage(channel, rtio_counter,
ExceptionType(exception_type))
def decode_dump(data):
2015-12-21 18:37:53 +08:00
parts = struct.unpack(">IQbbbb", data[:16])
(sent_bytes, total_byte_count,
overflow_occured, log_channel, dds_channel, _) = parts
2015-12-20 14:34:16 +08:00
if sent_bytes + 16 != len(data):
raise ValueError("analyzer dump has incorrect length")
if overflow_occured:
logger.warning("analyzer FIFO overflow occured, "
"some messages have been lost")
if total_byte_count > sent_bytes:
logger.info("analyzer ring buffer has wrapped %d times",
total_byte_count//sent_bytes)
position = 16
messages = []
for _ in range(sent_bytes//32):
messages.append(decode_message(data[position:position+32]))
position += 32
2015-12-23 18:57:53 +08:00
return messages, log_channel, dds_channel
2015-12-20 14:34:16 +08:00
2015-12-20 19:32:52 +08:00
def vcd_codes():
codechars = [chr(i) for i in range(33, 127)]
for n in count():
q, r = divmod(n, len(codechars))
code = codechars[r]
while q > 0:
q, r = divmod(q, len(codechars))
code = codechars[r] + code
yield code
class VCDChannel:
def __init__(self, out, code):
self.out = out
self.code = code
def set_value(self, value):
if len(value) > 1:
self.out.write("b" + value + " " + self.code + "\n")
else:
self.out.write(value + self.code + "\n")
2015-12-23 18:57:53 +08:00
def set_value_double(self, x):
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
self.set_value("{:064b}".format(integer_cast))
2015-12-20 19:32:52 +08:00
class VCDManager:
def __init__(self, filename):
self.out = open(filename, "w")
self.codes = vcd_codes()
self.current_time = None
2015-12-20 22:06:07 +08:00
def set_timescale_ns(self, timescale):
self.out.write("$timescale {}ns $end\n".format(timescale))
2015-12-20 19:32:52 +08:00
def get_channel(self, name, width):
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)
def set_time(self, time):
if time != self.current_time:
self.out.write("#{}\n".format(time))
self.current_time = time
def close(self):
self.out.close()
class TTLHandler:
def __init__(self, vcd_manager, name):
self.name = name
2015-12-20 19:32:52 +08:00
self.channel_value = vcd_manager.get_channel(name, 1)
self.last_value = "X"
self.oe = True
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("TTL write @%d %d to %d, name: %s",
message.timestamp, message.data, message.address, self.name)
2015-12-20 19:32:52 +08:00
if message.address == 0:
self.last_value = str(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
elif messages.address == 1:
self.oe = bool(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
else:
self.channel_value.set_value("X")
2015-12-23 18:57:53 +08:00
class DDSHandler:
def __init__(self, vcd_manager, dds_type, onehot_sel, sysclk):
self.vcd_manager = vcd_manager
self.dds_type = dds_type
self.onehot_sel = onehot_sel
self.sysclk = sysclk
self.selected_dds_channels = set()
self.dds_channels = dict()
def add_dds_channel(self, name, dds_channel_nr):
dds_channel = dict()
dds_channel["vcd_frequency"] = \
self.vcd_manager.get_channel(name + "/frequency", 64)
dds_channel["vcd_phase"] = \
self.vcd_manager.get_channel(name + "/phase", 64)
if self.dds_type == "AD9858":
dds_channel["ftw"] = [None, None, None, None]
dds_channel["pow"] = [None, None]
elif self.dds_type == "AD9914":
dds_channel["ftw"] = [None, None]
dds_channel["pow"] = None
self.dds_channels[dds_channel_nr] = dds_channel
def _gpio_to_channels(self, gpio):
gpio >>= 1 # strip reset
if self.onehot_sel:
r = set()
nr = 0
mask = 1
while gpio >= mask:
if gpio & mask:
r.add(nr)
nr += 1
mask *= 2
return r
else:
return {gpio}
def _decode_ad9858_write(self, message):
if message.address == 0x41:
self.selected_dds_channels = self._gpio_to_channels(message.data)
for dds_channel_nr in self.selected_dds_channels:
dds_channel = self.dds_channels[dds_channel_nr]
if message.address in range(0x0a, 0x0e):
dds_channel["ftw"][message.address - 0x0a] = message.data
elif message.address in range(0x0e, 0x10):
dds_channel["pow"][message.address - 0x0e] = message.data
elif message.address == 0x40: # FUD
if None not in dds_channel["ftw"]:
ftw = sum(x << i*8
for i, x in enumerate(dds_channel["ftw"]))
frequency = ftw*self.sysclk/2**32
dds_channel["vcd_frequency"].set_value_double(frequency)
if None not in dds_channel["pow"]:
pow = dds_channel["pow"][0] | (dds_channel["pow"][1] & 0x3f) << 8
phase = pow/2**14
dds_channel["vcd_phase"].set_value_double(phase)
def _decode_ad9914_write(self, message):
if message.address == 0x81:
self.selected_dds_channels = self._gpio_to_channels(message.data)
for dds_channel_nr in self.selected_dds_channels:
dds_channel = self.dds_channels[dds_channel_nr]
if message.address in range(0x2d, 0x2f):
dds_channel["ftw"][message.address - 0x2d] = message.data
elif message.address == 0x31:
dds_channel["pow"] = message.data
elif message.address == 0x80: # FUD
if None not in dds_channel["ftw"]:
ftw = sum(x << i*8
for i, x in enumerate(dds_channel["ftw"]))
frequency = ftw*self.sysclk/2**32
dds_channel["vcd_frequency"].set_value_double(frequency)
if dds_channel["pow"] is not None:
phase = dds_channel["pow"]/2**16
dds_channel["vcd_phase"].set_value_double(phase)
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("DDS write @%d 0x%04x to 0x%02x, selected channels: %s",
message.timestamp, message.data, message.address,
self.selected_dds_channels)
if self.dds_type == "AD9858":
self._decode_ad9858_write(message)
elif self.dds_type == "AD9914":
self._decode_ad9914_write(message)
2015-12-20 22:06:07 +08:00
def get_timescale(devices):
timescale = None
for desc in devices.values():
if isinstance(desc, dict) and desc["type"] == "local":
if (desc["module"] == "artiq.coredevice.core"
and desc["class"] == "Core"):
if timescale is None:
timescale = desc["arguments"]["ref_period"]*1e9
else:
return None # more than one core device found
return timescale
2015-12-23 18:57:53 +08:00
def create_channel_handlers(vcd_manager, devices, log_channel, dds_channel):
2015-12-20 19:32:52 +08:00
channel_handlers = dict()
for name, desc in sorted(devices.items(), key=itemgetter(0)):
if isinstance(desc, dict) and desc["type"] == "local":
2015-12-20 22:06:07 +08:00
if (desc["module"] == "artiq.coredevice.ttl"
and desc["class"] in {"TTLOut", "TTLInOut"}):
2015-12-20 19:32:52 +08:00
channel = desc["arguments"]["channel"]
channel_handlers[channel] = TTLHandler(vcd_manager, name)
2015-12-23 18:57:53 +08:00
if (desc["module"] == "artiq.coredevice.dds"
and desc["class"] in {"AD9858", "AD9914"}):
sysclk = desc["arguments"]["sysclk"]
dds_channel_ddsbus = desc["arguments"]["channel"]
if dds_channel in channel_handlers:
dds_handler = channel_handlers[dds_channel]
if dds_handler.dds_type != desc["class"]:
raise ValueError("All DDS channels must have the same type")
if dds_handler.sysclk != sysclk:
raise ValueError("All DDS channels must have the same sysclk")
else:
# Assume AD9914 systems use one-hot selection signals
# TODO: move one-hot flag declarations into a single place
dds_handler = DDSHandler(vcd_manager, desc["class"],
desc["class"] == "AD9914", sysclk)
channel_handlers[dds_channel] = dds_handler
dds_handler.add_dds_channel(name, dds_channel_ddsbus)
2015-12-20 19:32:52 +08:00
return channel_handlers
def get_message_time(message):
return getattr(message, "timestamp", message.rtio_counter)
2015-12-23 18:57:53 +08:00
def messages_to_vcd(filename, devices, messages, log_channel, dds_channel):
2015-12-20 19:32:52 +08:00
vcd_manager = VCDManager(filename)
try:
2015-12-20 22:06:07 +08:00
timescale = get_timescale(devices)
if timescale is not None:
vcd_manager.set_timescale_ns(timescale)
else:
logger.warning("unable to determine VCD timescale")
2015-12-23 18:57:53 +08:00
channel_handlers = create_channel_handlers(vcd_manager, devices,
log_channel, dds_channel)
2015-12-20 22:06:07 +08:00
2015-12-20 19:32:52 +08:00
vcd_manager.set_time(0)
2015-12-20 22:06:07 +08:00
messages = sorted(messages, key=get_message_time)
2015-12-20 19:32:52 +08:00
if messages:
start_time = get_message_time(messages[0])
for message in messages:
if message.channel in channel_handlers:
vcd_manager.set_time(
get_message_time(message) - start_time)
channel_handlers[message.channel].process_message(message)
finally:
vcd_manager.close()