1224 lines
39 KiB
Python
1224 lines
39 KiB
Python
# Copyright (c) 2013 ARM Limited
|
|
# All rights reserved
|
|
#
|
|
# The license below extends only to copyright in the software and shall
|
|
# not be construed as granting a license to any other intellectual
|
|
# property including but not limited to intellectual property relating
|
|
# to a hardware implementation of the functionality of the software
|
|
# licensed hereunder. You may use the software subject to the license
|
|
# terms below provided that you ensure that this notice is replicated
|
|
# unmodified and in its entirety in all distributions of the software,
|
|
# modified or unmodified, in source code or in binary form.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are
|
|
# met: redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer;
|
|
# redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution;
|
|
# neither the name of the copyright holders nor the names of its
|
|
# contributors may be used to endorse or promote products derived from
|
|
# this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import os
|
|
import re
|
|
from time import time as wall_time
|
|
|
|
from . import (
|
|
blobs,
|
|
colours,
|
|
parse,
|
|
)
|
|
from .colours import unknownColour
|
|
from .point import Point
|
|
|
|
id_parts = "TSPLFE"
|
|
|
|
all_ids = set(id_parts)
|
|
no_ids = set()
|
|
|
|
|
|
class BlobDataSelect:
|
|
"""Represents which data is displayed for Ided object"""
|
|
|
|
def __init__(self):
|
|
# Copy all_ids
|
|
self.ids = set(all_ids)
|
|
|
|
def __and__(self, rhs):
|
|
"""And for filtering"""
|
|
ret = BlobDataSelect()
|
|
ret.ids = self.ids.intersection(rhs.ids)
|
|
return ret
|
|
|
|
|
|
class BlobVisualData:
|
|
"""Super class for block data colouring"""
|
|
|
|
def to_striped_block(self, select):
|
|
"""Return an array of colours to use for a striped block"""
|
|
return unknownColour
|
|
|
|
def get_inst(self):
|
|
"""Get an instruction Id (if any) from this data"""
|
|
return None
|
|
|
|
def get_line(self):
|
|
"""Get a line Id (if any) from this data"""
|
|
return None
|
|
|
|
def __repr__(self):
|
|
return (
|
|
self.__class__.__name__ + "().from_string(" + self.__str__() + ")"
|
|
)
|
|
|
|
def __str__(self):
|
|
return ""
|
|
|
|
|
|
class Id(BlobVisualData):
|
|
"""A line or instruction id"""
|
|
|
|
def __init__(self):
|
|
self.isFault = False
|
|
self.threadId = 0
|
|
self.streamSeqNum = 0
|
|
self.predictionSeqNum = 0
|
|
self.lineSeqNum = 0
|
|
self.fetchSeqNum = 0
|
|
self.execSeqNum = 0
|
|
|
|
def as_list(self):
|
|
return [
|
|
self.threadId,
|
|
self.streamSeqNum,
|
|
self.predictionSeqNum,
|
|
self.lineSeqNum,
|
|
self.fetchSeqNum,
|
|
self.execSeqNum,
|
|
]
|
|
|
|
def __cmp__(self, right):
|
|
return cmp(self.as_list(), right.as_list())
|
|
|
|
def from_string(self, string):
|
|
m = re.match(
|
|
r"^(F;)?(\d+)/(\d+)\.(\d+)/(\d+)(/(\d+)(\.(\d+))?)?", string
|
|
)
|
|
|
|
def seqnum_from_string(string):
|
|
if string is None:
|
|
return 0
|
|
else:
|
|
return int(string)
|
|
|
|
if m is None:
|
|
print("Invalid Id string", string)
|
|
else:
|
|
elems = m.groups()
|
|
|
|
if elems[0] is not None:
|
|
self.isFault = True
|
|
else:
|
|
self.isFault = False
|
|
|
|
self.threadId = seqnum_from_string(elems[1])
|
|
self.streamSeqNum = seqnum_from_string(elems[2])
|
|
self.predictionSeqNum = seqnum_from_string(elems[3])
|
|
self.lineSeqNum = seqnum_from_string(elems[4])
|
|
self.fetchSeqNum = seqnum_from_string(elems[6])
|
|
self.execSeqNum = seqnum_from_string(elems[8])
|
|
return self
|
|
|
|
def get_inst(self):
|
|
if self.fetchSeqNum != 0:
|
|
return self
|
|
else:
|
|
return None
|
|
|
|
def get_line(self):
|
|
return self
|
|
|
|
def __str__(self):
|
|
"""Returns the usual id T/S.P/L/F.E string"""
|
|
return (
|
|
str(self.threadId)
|
|
+ "/"
|
|
+ str(self.streamSeqNum)
|
|
+ "."
|
|
+ str(self.predictionSeqNum)
|
|
+ "/"
|
|
+ str(self.lineSeqNum)
|
|
+ "/"
|
|
+ str(self.fetchSeqNum)
|
|
+ "."
|
|
+ str(self.execSeqNum)
|
|
)
|
|
|
|
def to_striped_block(self, select):
|
|
ret = []
|
|
|
|
if self.isFault:
|
|
ret.append(colours.faultColour)
|
|
|
|
if "T" in select.ids:
|
|
ret.append(colours.number_to_colour(self.threadId))
|
|
if "S" in select.ids:
|
|
ret.append(colours.number_to_colour(self.streamSeqNum))
|
|
if "P" in select.ids:
|
|
ret.append(colours.number_to_colour(self.predictionSeqNum))
|
|
if "L" in select.ids:
|
|
ret.append(colours.number_to_colour(self.lineSeqNum))
|
|
if self.fetchSeqNum != 0 and "F" in select.ids:
|
|
ret.append(colours.number_to_colour(self.fetchSeqNum))
|
|
if self.execSeqNum != 0 and "E" in select.ids:
|
|
ret.append(colours.number_to_colour(self.execSeqNum))
|
|
|
|
if len(ret) == 0:
|
|
ret = [colours.unknownColour]
|
|
|
|
if self.isFault:
|
|
ret.append(colours.faultColour)
|
|
|
|
return ret
|
|
|
|
|
|
class Branch(BlobVisualData):
|
|
"""Branch data new stream and prediction sequence numbers, a branch
|
|
reason and a new PC"""
|
|
|
|
def __init__(self):
|
|
self.newStreamSeqNum = 0
|
|
self.newPredictionSeqNum = 0
|
|
self.newPC = 0
|
|
self.reason = "NoBranch"
|
|
self.id = Id()
|
|
|
|
def from_string(self, string):
|
|
m = re.match(r"^(\w+);(\d+)\.(\d+);([0-9a-fA-Fx]+);(.*)$", string)
|
|
|
|
if m is not None:
|
|
(
|
|
self.reason,
|
|
newStreamSeqNum,
|
|
newPredictionSeqNum,
|
|
newPC,
|
|
id,
|
|
) = m.groups()
|
|
|
|
self.newStreamSeqNum = int(newStreamSeqNum)
|
|
self.newPredictionSeqNum = int(newPredictionSeqNum)
|
|
self.newPC = int(newPC, 0)
|
|
self.id = special_view_decoder(Id)(id)
|
|
# self.branch = special_view_decoder(Branch)(branch)
|
|
else:
|
|
print("Bad Branch data:", string)
|
|
return self
|
|
|
|
def to_striped_block(self, select):
|
|
return [
|
|
colours.number_to_colour(self.newStreamSeqNum),
|
|
colours.number_to_colour(self.newPredictionSeqNum),
|
|
colours.number_to_colour(self.newPC),
|
|
]
|
|
|
|
|
|
class Counts(BlobVisualData):
|
|
"""Treat the input data as just a /-separated list of count values (or
|
|
just a single value)"""
|
|
|
|
def __init__(self):
|
|
self.counts = []
|
|
|
|
def from_string(self, string):
|
|
self.counts = list(map(int, re.split("/", string)))
|
|
return self
|
|
|
|
def to_striped_block(self, select):
|
|
return list(map(colours.number_to_colour, self.counts))
|
|
|
|
|
|
class Colour(BlobVisualData):
|
|
"""A fixed colour block, used for special colour decoding"""
|
|
|
|
def __init__(self, colour):
|
|
self.colour = colour
|
|
|
|
def to_striped_block(self, select):
|
|
return [self.colour]
|
|
|
|
|
|
class DcacheAccess(BlobVisualData):
|
|
"""Data cache accesses [RW];id"""
|
|
|
|
def __init__(self):
|
|
self.direc = "R"
|
|
self.id = Id()
|
|
|
|
def from_string(self, string):
|
|
self.direc, id = re.match("^([RW]);([^;]*);.*$", string).groups()
|
|
self.id.from_string(id)
|
|
return self
|
|
|
|
def get_inst(self):
|
|
return self.id
|
|
|
|
def to_striped_block(self, select):
|
|
if self.direc == "R":
|
|
direc_colour = colours.readColour
|
|
elif self.direc == "R":
|
|
direc_colour = colours.writeColour
|
|
else:
|
|
direc_colour = colours.errorColour
|
|
return [direc_colour] + self.id.to_striped_block(select)
|
|
|
|
|
|
class ColourPattern:
|
|
"""Super class for decoders that make 2D grids rather than just single
|
|
striped blocks"""
|
|
|
|
def elems(self):
|
|
return []
|
|
|
|
def to_striped_block(self, select):
|
|
return [[[colours.errorColour]]]
|
|
|
|
|
|
def special_view_decoder(class_):
|
|
"""Generate a decode function that checks for special character
|
|
arguments first (and generates a fixed colour) before building a
|
|
BlobVisualData of the given class"""
|
|
|
|
def decode(symbol):
|
|
if symbol in special_state_colours:
|
|
return Colour(special_state_colours[symbol])
|
|
else:
|
|
return class_().from_string(symbol)
|
|
|
|
return decode
|
|
|
|
|
|
class TwoDColours(ColourPattern):
|
|
"""A 2D grid pattern decoder"""
|
|
|
|
def __init__(self, blockss):
|
|
self.blockss = blockss
|
|
|
|
@classmethod
|
|
def decoder(class_, elemClass, dataName):
|
|
"""Factory for making decoders for particular block types"""
|
|
|
|
def decode(pairs):
|
|
if dataName not in pairs:
|
|
print(
|
|
"TwoDColours: no event data called:",
|
|
dataName,
|
|
"in:",
|
|
pairs,
|
|
)
|
|
return class_([[Colour(colours.errorColour)]])
|
|
else:
|
|
parsed = parse.list_parser(pairs[dataName])
|
|
return class_(
|
|
parse.map2(special_view_decoder(elemClass), parsed)
|
|
)
|
|
|
|
return decode
|
|
|
|
@classmethod
|
|
def indexed_decoder(class_, elemClass, dataName, picPairs):
|
|
"""Factory for making decoders for particular block types but
|
|
where the list elements are pairs of (index, data) and
|
|
strip and stripelems counts are picked up from the pair
|
|
data on the decoder's picture file. This gives a 2D layout
|
|
of the values with index 0 at strip=0, elem=0 and index 1
|
|
at strip=0, elem=1"""
|
|
|
|
def decode(pairs):
|
|
if dataName not in pairs:
|
|
print(
|
|
"TwoDColours: no event data called:",
|
|
dataName,
|
|
"in:",
|
|
pairs,
|
|
)
|
|
return class_([[Colour(colours.errorColour)]])
|
|
else:
|
|
strips = int(picPairs["strips"])
|
|
strip_elems = int(picPairs["stripelems"])
|
|
|
|
raw_iv_pairs = pairs[dataName]
|
|
|
|
parsed = parse.parse_indexed_list(raw_iv_pairs)
|
|
|
|
array = [
|
|
[
|
|
Colour(colours.emptySlotColour)
|
|
for i in range(0, strip_elems)
|
|
]
|
|
for j in range(0, strips)
|
|
]
|
|
|
|
for index, value in parsed:
|
|
try:
|
|
array[index % strips][index / strips] = (
|
|
special_view_decoder(elemClass)(value)
|
|
)
|
|
except:
|
|
print(
|
|
"Element out of range strips: %d,"
|
|
" stripelems %d, index: %d"
|
|
% (strips, strip_elems, index)
|
|
)
|
|
|
|
# return class_(array)
|
|
return class_(array)
|
|
|
|
return decode
|
|
|
|
def elems(self):
|
|
"""Get a flat list of all elements"""
|
|
ret = []
|
|
for blocks in self.blockss:
|
|
ret += blocks
|
|
return ret
|
|
|
|
def to_striped_block(self, select):
|
|
return parse.map2(lambda d: d.to_striped_block(select), self.blockss)
|
|
|
|
|
|
class FrameColours(ColourPattern):
|
|
"""Decode to a 2D grid which has a single occupied row from the event
|
|
data and some blank rows forming a frame with the occupied row as a
|
|
'title' coloured stripe"""
|
|
|
|
def __init__(self, block, numBlankSlots):
|
|
self.numBlankSlots = numBlankSlots
|
|
self.block = block
|
|
|
|
@classmethod
|
|
def decoder(class_, elemClass, numBlankSlots, dataName):
|
|
"""Factory for element type"""
|
|
|
|
def decode(pairs):
|
|
if dataName not in pairs:
|
|
print(
|
|
"FrameColours: no event data called:",
|
|
dataName,
|
|
"in:",
|
|
pairs,
|
|
)
|
|
return class_([Colour(colours.errorColour)])
|
|
else:
|
|
parsed = parse.list_parser(pairs[dataName])
|
|
return class_(
|
|
special_view_decoder(elemClass)(parsed[0][0]),
|
|
numBlankSlots,
|
|
)
|
|
|
|
return decode
|
|
|
|
def elems(self):
|
|
return [self.block]
|
|
|
|
def to_striped_block(self, select):
|
|
return [[self.block.to_striped_block(select)]] + (
|
|
self.numBlankSlots * [[[colours.backgroundColour]]]
|
|
)
|
|
|
|
|
|
special_state_colours = {
|
|
"U": colours.unknownColour,
|
|
"B": colours.blockedColour,
|
|
"-": colours.bubbleColour,
|
|
"": colours.emptySlotColour,
|
|
"E": colours.emptySlotColour,
|
|
"R": colours.reservedSlotColour,
|
|
"X": colours.errorColour,
|
|
"F": colours.faultColour,
|
|
"r": colours.readColour,
|
|
"w": colours.writeColour,
|
|
}
|
|
|
|
special_state_names = {
|
|
"U": "(U)nknown",
|
|
"B": "(B)locked",
|
|
"-": "(-)Bubble",
|
|
"": "()Empty",
|
|
"E": "(E)mpty",
|
|
"R": "(R)eserved",
|
|
"X": "(X)Error",
|
|
"F": "(F)ault",
|
|
"r": "(r)ead",
|
|
"w": "(w)rite",
|
|
}
|
|
|
|
special_state_chars = list(special_state_colours.keys())
|
|
|
|
# The complete set of available block data types
|
|
decoder_element_classes = {
|
|
"insts": Id,
|
|
"lines": Id,
|
|
"branch": Branch,
|
|
"dcache": DcacheAccess,
|
|
"counts": Counts,
|
|
}
|
|
|
|
indexed_decoder_element_classes = {"indexedCounts": Counts}
|
|
|
|
|
|
def find_colour_decoder(stripSpace, decoderName, dataName, picPairs):
|
|
"""Make a colour decoder from some picture file blob attributes"""
|
|
if decoderName == "frame":
|
|
return FrameColours.decoder(Counts, stripSpace, dataName)
|
|
elif decoderName in decoder_element_classes:
|
|
return TwoDColours.decoder(
|
|
decoder_element_classes[decoderName], dataName
|
|
)
|
|
elif decoderName in indexed_decoder_element_classes:
|
|
return TwoDColours.indexed_decoder(
|
|
indexed_decoder_element_classes[decoderName], dataName, picPairs
|
|
)
|
|
else:
|
|
return None
|
|
|
|
|
|
class IdedObj:
|
|
"""An object identified by an Id carrying paired data.
|
|
The super class for Inst and Line"""
|
|
|
|
def __init__(self, id, pairs={}):
|
|
self.id = id
|
|
self.pairs = pairs
|
|
|
|
def __cmp__(self, right):
|
|
return cmp(self.id, right.id)
|
|
|
|
def table_line(self):
|
|
"""Represent the object as a list of table row data"""
|
|
return []
|
|
|
|
# FIXME, add a table column titles?
|
|
|
|
def __repr__(self):
|
|
return " ".join(self.table_line())
|
|
|
|
|
|
class Inst(IdedObj):
|
|
"""A non-fault instruction"""
|
|
|
|
def __init__(self, id, disassembly, addr, pairs={}):
|
|
super().__init__(id, pairs)
|
|
if "nextAddr" in pairs:
|
|
self.nextAddr = int(pairs["nextAddr"], 0)
|
|
del pairs["nextAddr"]
|
|
else:
|
|
self.nextAddr = None
|
|
self.disassembly = disassembly
|
|
self.addr = addr
|
|
|
|
def table_line(self):
|
|
if self.nextAddr is not None:
|
|
addrStr = f"0x{self.addr:x}->0x{self.nextAddr:x}"
|
|
else:
|
|
addrStr = f"0x{self.addr:x}"
|
|
ret = [addrStr, self.disassembly]
|
|
for name, value in self.pairs.items():
|
|
ret.append(f"{name}={str(value)}")
|
|
return ret
|
|
|
|
|
|
class InstFault(IdedObj):
|
|
"""A fault instruction"""
|
|
|
|
def __init__(self, id, fault, addr, pairs={}):
|
|
super().__init__(id, pairs)
|
|
self.fault = fault
|
|
self.addr = addr
|
|
|
|
def table_line(self):
|
|
ret = [f"0x{self.addr:x}", self.fault]
|
|
for name, value in self.pairs:
|
|
ret.append("%s=%s", name, str(value))
|
|
return ret
|
|
|
|
|
|
class Line(IdedObj):
|
|
"""A fetched line"""
|
|
|
|
def __init__(self, id, vaddr, paddr, size, pairs={}):
|
|
super().__init__(id, pairs)
|
|
self.vaddr = vaddr
|
|
self.paddr = paddr
|
|
self.size = size
|
|
|
|
def table_line(self):
|
|
ret = [f"0x{self.vaddr:x}/0x{self.paddr:x}", "%d" % self.size]
|
|
for name, value in self.pairs:
|
|
ret.append("%s=%s", name, str(value))
|
|
return ret
|
|
|
|
|
|
class LineFault(IdedObj):
|
|
"""A faulting line"""
|
|
|
|
def __init__(self, id, fault, vaddr, pairs={}):
|
|
super().__init__(id, pairs)
|
|
self.vaddr = vaddr
|
|
self.fault = fault
|
|
|
|
def table_line(self):
|
|
ret = [f"0x{self.vaddr:x}", self.fault]
|
|
for name, value in self.pairs:
|
|
ret.append("%s=%s", name, str(value))
|
|
return ret
|
|
|
|
|
|
class BlobEvent:
|
|
"""Time event for a single blob"""
|
|
|
|
def __init__(self, unit, time, pairs={}):
|
|
# blob's unit name
|
|
self.unit = unit
|
|
self.time = time
|
|
# dict of picChar (blob name) to visual data
|
|
self.visuals = {}
|
|
# Miscellaneous unparsed MinorTrace line data
|
|
self.pairs = pairs
|
|
# Non-MinorTrace debug printout for this unit at this time
|
|
self.comments = []
|
|
|
|
def find_ided_objects(self, model, picChar, includeInstLines):
|
|
"""Find instructions/lines mentioned in the blob's event
|
|
data"""
|
|
ret = []
|
|
if picChar in self.visuals:
|
|
blocks = self.visuals[picChar].elems()
|
|
|
|
def find_inst(data):
|
|
instId = data.get_inst()
|
|
lineId = data.get_line()
|
|
if instId is not None:
|
|
inst = model.find_inst(instId)
|
|
line = model.find_line(instId)
|
|
if inst is not None:
|
|
ret.append(inst)
|
|
if includeInstLines and line is not None:
|
|
ret.append(line)
|
|
elif lineId is not None:
|
|
line = model.find_line(lineId)
|
|
if line is not None:
|
|
ret.append(line)
|
|
|
|
list(map(find_inst, blocks))
|
|
return sorted(ret)
|
|
|
|
|
|
class BlobModel:
|
|
"""Model bringing together blob definitions and parsed events"""
|
|
|
|
def __init__(self, unitNamePrefix=""):
|
|
self.blobs = []
|
|
self.unitNameToBlobs = {}
|
|
self.unitEvents = {}
|
|
self.clear_events()
|
|
self.picSize = Point(20, 10)
|
|
self.lastTime = 0
|
|
self.unitNamePrefix = unitNamePrefix
|
|
|
|
def clear_events(self):
|
|
"""Drop all events and times"""
|
|
self.lastTime = 0
|
|
self.times = []
|
|
self.insts = {}
|
|
self.lines = {}
|
|
self.numEvents = 0
|
|
|
|
for unit, events in self.unitEvents.items():
|
|
self.unitEvents[unit] = []
|
|
|
|
def add_blob(self, blob):
|
|
"""Add a parsed blob to the model"""
|
|
self.blobs.append(blob)
|
|
if blob.unit not in self.unitNameToBlobs:
|
|
self.unitNameToBlobs[blob.unit] = []
|
|
|
|
self.unitNameToBlobs[blob.unit].append(blob)
|
|
|
|
def add_inst(self, inst):
|
|
"""Add a MinorInst instruction definition to the model"""
|
|
# Is this a non micro-op instruction. Microops (usually) get their
|
|
# fetchSeqNum == 0 varient stored first
|
|
macroop_key = (inst.id.fetchSeqNum, 0)
|
|
full_key = (inst.id.fetchSeqNum, inst.id.execSeqNum)
|
|
|
|
if inst.id.execSeqNum != 0 and macroop_key not in self.insts:
|
|
self.insts[macroop_key] = inst
|
|
|
|
self.insts[full_key] = inst
|
|
|
|
def find_inst(self, id):
|
|
"""Find an instruction either as a microop or macroop"""
|
|
macroop_key = (id.fetchSeqNum, 0)
|
|
full_key = (id.fetchSeqNum, id.execSeqNum)
|
|
|
|
if full_key in self.insts:
|
|
return self.insts[full_key]
|
|
elif macroop_key in self.insts:
|
|
return self.insts[macroop_key]
|
|
else:
|
|
return None
|
|
|
|
def add_line(self, line):
|
|
"""Add a MinorLine line to the model"""
|
|
self.lines[line.id.lineSeqNum] = line
|
|
|
|
def add_unit_event(self, event):
|
|
"""Add a single event to the model. This must be an event at a
|
|
time >= the current maximum time"""
|
|
if event.unit in self.unitEvents:
|
|
events = self.unitEvents[event.unit]
|
|
if len(events) > 0 and events[len(events) - 1].time > event.time:
|
|
print("Bad event ordering")
|
|
events.append(event)
|
|
self.numEvents += 1
|
|
self.lastTime = max(self.lastTime, event.time)
|
|
|
|
def extract_times(self):
|
|
"""Extract a list of all the times from the seen events. Call after
|
|
reading events to give a safe index list to use for time indices"""
|
|
times = {}
|
|
for unitEvents in self.unitEvents.values():
|
|
for event in unitEvents:
|
|
times[event.time] = 1
|
|
self.times = list(times.keys())
|
|
self.times.sort()
|
|
|
|
def find_line(self, id):
|
|
"""Find a line by id"""
|
|
key = id.lineSeqNum
|
|
return self.lines.get(key, None)
|
|
|
|
def find_event_bisection(
|
|
self, unit, time, events, lower_index, upper_index
|
|
):
|
|
"""Find an event by binary search on time indices"""
|
|
while lower_index <= upper_index:
|
|
pivot = (upper_index + lower_index) / 2
|
|
pivotEvent = events[pivot]
|
|
event_equal = pivotEvent.time == time or (
|
|
pivotEvent.time < time
|
|
and (pivot == len(events) - 1 or events[pivot + 1].time > time)
|
|
)
|
|
|
|
if event_equal:
|
|
return pivotEvent
|
|
elif time > pivotEvent.time:
|
|
if pivot == upper_index:
|
|
return None
|
|
else:
|
|
lower_index = pivot + 1
|
|
elif time < pivotEvent.time:
|
|
if pivot == lower_index:
|
|
return None
|
|
else:
|
|
upper_index = pivot - 1
|
|
else:
|
|
return None
|
|
return None
|
|
|
|
def find_unit_event_by_time(self, unit, time):
|
|
"""Find the last event for the given unit at time <= time"""
|
|
if unit in self.unitEvents:
|
|
events = self.unitEvents[unit]
|
|
ret = self.find_event_bisection(
|
|
unit, time, events, 0, len(events) - 1
|
|
)
|
|
|
|
return ret
|
|
else:
|
|
return None
|
|
|
|
def find_time_index(self, time):
|
|
"""Find a time index close to the given time (where
|
|
times[return] <= time and times[return+1] > time"""
|
|
ret = 0
|
|
lastIndex = len(self.times) - 1
|
|
while ret < lastIndex and self.times[ret + 1] <= time:
|
|
ret += 1
|
|
return ret
|
|
|
|
def add_minor_inst(self, rest):
|
|
"""Parse and add a MinorInst line to the model"""
|
|
pairs = parse.parse_pairs(rest)
|
|
other_pairs = dict(pairs)
|
|
|
|
id = Id().from_string(pairs["id"])
|
|
del other_pairs["id"]
|
|
|
|
addr = int(pairs["addr"], 0)
|
|
del other_pairs["addr"]
|
|
|
|
if "inst" in other_pairs:
|
|
del other_pairs["inst"]
|
|
|
|
# Collapse unnecessary spaces in disassembly
|
|
disassembly = re.sub(" *", " ", re.sub("^ *", "", pairs["inst"]))
|
|
|
|
inst = Inst(id, disassembly, addr, other_pairs)
|
|
self.add_inst(inst)
|
|
elif "fault" in other_pairs:
|
|
del other_pairs["fault"]
|
|
|
|
inst = InstFault(id, pairs["fault"], addr, other_pairs)
|
|
|
|
self.add_inst(inst)
|
|
|
|
def add_minor_line(self, rest):
|
|
"""Parse and add a MinorLine line to the model"""
|
|
pairs = parse.parse_pairs(rest)
|
|
other_pairs = dict(pairs)
|
|
|
|
id = Id().from_string(pairs["id"])
|
|
del other_pairs["id"]
|
|
|
|
vaddr = int(pairs["vaddr"], 0)
|
|
del other_pairs["vaddr"]
|
|
|
|
if "paddr" in other_pairs:
|
|
del other_pairs["paddr"]
|
|
del other_pairs["size"]
|
|
paddr = int(pairs["paddr"], 0)
|
|
size = int(pairs["size"], 0)
|
|
|
|
self.add_line(Line(id, vaddr, paddr, size, other_pairs))
|
|
elif "fault" in other_pairs:
|
|
del other_pairs["fault"]
|
|
|
|
self.add_line(LineFault(id, pairs["fault"], vaddr, other_pairs))
|
|
|
|
def load_events(self, file, startTime=0, endTime=None):
|
|
"""Load an event file and add everything to this model"""
|
|
|
|
def update_comments(comments, time):
|
|
# Add a list of comments to an existing event, if there is one at
|
|
# the given time, or create a new, correctly-timed, event from
|
|
# the last event and attach the comments to that
|
|
for commentUnit, commentRest in comments:
|
|
event = self.find_unit_event_by_time(commentUnit, time)
|
|
# Find an event to which this comment can be attached
|
|
if event is None:
|
|
# No older event, make a new empty one
|
|
event = BlobEvent(commentUnit, time, {})
|
|
self.add_unit_event(event)
|
|
elif event.time != time:
|
|
# Copy the old event and make a new one with the right
|
|
# time and comment
|
|
newEvent = BlobEvent(commentUnit, time, event.pairs)
|
|
newEvent.visuals = dict(event.visuals)
|
|
event = newEvent
|
|
self.add_unit_event(event)
|
|
event.comments.append(commentRest)
|
|
|
|
self.clear_events()
|
|
|
|
# A negative time will *always* be different from an event time
|
|
time = -1
|
|
time_events = {}
|
|
last_time_lines = {}
|
|
minor_trace_line_count = 0
|
|
comments = []
|
|
|
|
default_colour = [[colours.unknownColour]]
|
|
next_progress_print_event_count = 1000
|
|
|
|
if not os.access(file, os.R_OK):
|
|
print("Can't open file", file)
|
|
exit(1)
|
|
else:
|
|
print("Opening file", file)
|
|
|
|
f = open(file)
|
|
|
|
start_wall_time = wall_time()
|
|
|
|
# Skip leading events
|
|
still_skipping = True
|
|
l = f.readline()
|
|
while l and still_skipping:
|
|
match = re.match(r"^\s*(\d+):", l)
|
|
if match is not None:
|
|
event_time = match.groups()
|
|
if int(event_time[0]) >= startTime:
|
|
still_skipping = False
|
|
else:
|
|
l = f.readline()
|
|
else:
|
|
l = f.readline()
|
|
|
|
match_line_re = re.compile(
|
|
r"^\s*(\d+):\s*([\w\.]+):\s*(Minor\w+:)?\s*(.*)$"
|
|
)
|
|
|
|
# Parse each line of the events file, accumulating comments to be
|
|
# attached to MinorTrace events when the time changes
|
|
reached_end_time = False
|
|
while not reached_end_time and l:
|
|
match = match_line_re.match(l)
|
|
if match is not None:
|
|
event_time, unit, line_type, rest = match.groups()
|
|
event_time = int(event_time)
|
|
|
|
unit = re.sub(
|
|
"^" + self.unitNamePrefix + r"\.?(.*)$", "\\1", unit
|
|
)
|
|
|
|
# When the time changes, resolve comments
|
|
if event_time != time:
|
|
if self.numEvents > next_progress_print_event_count:
|
|
print("Parsed to time: %d" % event_time)
|
|
next_progress_print_event_count = self.numEvents + 1000
|
|
update_comments(comments, time)
|
|
comments = []
|
|
time = event_time
|
|
|
|
if line_type is None:
|
|
# Treat this line as just a 'comment'
|
|
comments.append((unit, rest))
|
|
elif line_type == "MinorTrace:":
|
|
minor_trace_line_count += 1
|
|
|
|
# Only insert this event if it's not the same as
|
|
# the last event we saw for this unit
|
|
if last_time_lines.get(unit, None) != rest:
|
|
event = BlobEvent(unit, event_time, {})
|
|
pairs = parse.parse_pairs(rest)
|
|
event.pairs = pairs
|
|
|
|
# Try to decode the colour data for this event
|
|
blobs = self.unitNameToBlobs.get(unit, [])
|
|
for blob in blobs:
|
|
if blob.visualDecoder is not None:
|
|
event.visuals[blob.picChar] = (
|
|
blob.visualDecoder(pairs)
|
|
)
|
|
|
|
self.add_unit_event(event)
|
|
last_time_lines[unit] = rest
|
|
elif line_type == "MinorInst:":
|
|
self.add_minor_inst(rest)
|
|
elif line_type == "MinorLine:":
|
|
self.add_minor_line(rest)
|
|
|
|
if endTime is not None and time > endTime:
|
|
reached_end_time = True
|
|
|
|
l = f.readline()
|
|
|
|
update_comments(comments, time)
|
|
self.extract_times()
|
|
f.close()
|
|
|
|
end_wall_time = wall_time()
|
|
|
|
print(
|
|
"Total events:",
|
|
minor_trace_line_count,
|
|
"unique events:",
|
|
self.numEvents,
|
|
)
|
|
print("Time to parse:", end_wall_time - start_wall_time)
|
|
|
|
def add_blob_picture(self, offset, pic, nameDict):
|
|
"""Add a parsed ASCII-art pipeline markup to the model"""
|
|
pic_width = 0
|
|
for line in pic:
|
|
pic_width = max(pic_width, len(line))
|
|
pic_height = len(pic)
|
|
|
|
# Number of horizontal characters per 'pixel'. Should be 2
|
|
charsPerPixel = 2
|
|
|
|
# Clean up pic_width to a multiple of charsPerPixel
|
|
pic_width = (pic_width + charsPerPixel - 1) // 2
|
|
|
|
self.picSize = Point(pic_width, pic_height)
|
|
|
|
def pic_at(point):
|
|
"""Return the char pair at the given point.
|
|
Returns None for characters off the picture"""
|
|
x, y = point.to_pair()
|
|
x *= 2
|
|
if y >= len(pic) or x >= len(pic[y]):
|
|
return None
|
|
else:
|
|
return pic[y][x : x + charsPerPixel]
|
|
|
|
def clear_pic_at(point):
|
|
"""Clear the chars at point so we don't trip over them again"""
|
|
line = pic[point.y]
|
|
x = point.x * charsPerPixel
|
|
pic[point.y] = (
|
|
line[0:x] + (" " * charsPerPixel) + line[x + charsPerPixel :]
|
|
)
|
|
|
|
def skip_same_char(start, increment):
|
|
"""Skip characters which match pic_at(start)"""
|
|
char = pic_at(start)
|
|
hunt = start
|
|
while pic_at(hunt) == char:
|
|
hunt += increment
|
|
return hunt
|
|
|
|
def find_size(start):
|
|
"""Find the size of a rectangle with top left hand corner at
|
|
start consisting of (at least) a -. shaped corner describing
|
|
the top right corner of a rectangle of the same char"""
|
|
char = pic_at(start)
|
|
hunt_x = skip_same_char(start, Point(1, 0))
|
|
hunt_y = skip_same_char(start, Point(0, 1))
|
|
off_bottom_right = (hunt_x * Point(1, 0)) + (hunt_y * Point(0, 1))
|
|
return off_bottom_right - start
|
|
|
|
def point_return(point):
|
|
"""Carriage return, line feed"""
|
|
return Point(0, point.y + 1)
|
|
|
|
def find_arrow(start):
|
|
"""Find a simple 1-char wide arrow"""
|
|
|
|
def body(endChar, contChar, direc):
|
|
arrow_point = start
|
|
arrow_point += Point(0, 1)
|
|
clear_pic_at(start)
|
|
while pic_at(arrow_point) == contChar:
|
|
clear_pic_at(arrow_point)
|
|
arrow_point += Point(0, 1)
|
|
|
|
if pic_at(arrow_point) == endChar:
|
|
clear_pic_at(arrow_point)
|
|
self.add_blob(
|
|
blobs.Arrow(
|
|
"_",
|
|
start + offset,
|
|
direc=direc,
|
|
size=(Point(1, 1) + arrow_point - start),
|
|
)
|
|
)
|
|
else:
|
|
print("Bad arrow", start)
|
|
|
|
char = pic_at(start)
|
|
if char == "-\\":
|
|
body("-/", " :", "right")
|
|
elif char == "/-":
|
|
body("\\-", ": ", "left")
|
|
|
|
blank_chars = [" ", " :", ": "]
|
|
|
|
# Traverse the picture left to right, top to bottom to find blobs
|
|
seen_dict = {}
|
|
point = Point(0, 0)
|
|
while pic_at(point) is not None:
|
|
while pic_at(point) is not None:
|
|
char = pic_at(point)
|
|
if char == "->":
|
|
self.add_blob(
|
|
blobs.Arrow("_", point + offset, direc="right")
|
|
)
|
|
elif char == "<-":
|
|
self.add_blob(
|
|
blobs.Arrow("_", point + offset, direc="left")
|
|
)
|
|
elif char == "-\\" or char == "/-":
|
|
find_arrow(point)
|
|
elif char in blank_chars:
|
|
pass
|
|
else:
|
|
if char not in seen_dict:
|
|
size = find_size(point)
|
|
topLeft = point + offset
|
|
if char not in nameDict:
|
|
# Unnamed blobs
|
|
self.add_blob(
|
|
blobs.Block(
|
|
char,
|
|
nameDict.get(char, "_"),
|
|
topLeft,
|
|
size=size,
|
|
)
|
|
)
|
|
else:
|
|
# Named blobs, set visual info.
|
|
blob = nameDict[char]
|
|
blob.size = size
|
|
blob.topLeft = topLeft
|
|
self.add_blob(blob)
|
|
seen_dict[char] = True
|
|
point = skip_same_char(point, Point(1, 0))
|
|
point = point_return(point)
|
|
|
|
def load_picture(self, filename):
|
|
"""Load a picture file into the model"""
|
|
|
|
def parse_blob_description(char, unit, macros, pairsList):
|
|
# Parse the name value pairs in a blob-describing line
|
|
def expand_macros(pairs, newPairs):
|
|
# Recursively expand macros
|
|
for name, value in newPairs:
|
|
if name in macros:
|
|
expand_macros(pairs, macros[name])
|
|
else:
|
|
pairs[name] = value
|
|
return pairs
|
|
|
|
pairs = expand_macros({}, pairsList)
|
|
|
|
ret = None
|
|
|
|
typ = pairs.get("type", "block")
|
|
colour = colours.name_to_colour(pairs.get("colour", "black"))
|
|
|
|
if typ == "key":
|
|
ret = blobs.Key(char, unit, Point(0, 0), colour)
|
|
elif typ == "block":
|
|
ret = blobs.Block(char, unit, Point(0, 0), colour)
|
|
else:
|
|
print("Bad picture blog type:", typ)
|
|
|
|
if "hideId" in pairs:
|
|
hide = pairs["hideId"]
|
|
ret.dataSelect.ids -= set(hide)
|
|
|
|
if typ == "block":
|
|
ret.displayName = pairs.get("name", unit)
|
|
ret.nameLoc = pairs.get("nameLoc", "top")
|
|
ret.shape = pairs.get("shape", "box")
|
|
ret.stripDir = pairs.get("stripDir", "horiz")
|
|
ret.stripOrd = pairs.get("stripOrd", "LR")
|
|
ret.blankStrips = int(pairs.get("blankStrips", "0"))
|
|
ret.shorten = int(pairs.get("shorten", "0"))
|
|
|
|
if "decoder" in pairs:
|
|
decoderName = pairs["decoder"]
|
|
dataElement = pairs.get("dataElement", decoderName)
|
|
|
|
decoder = find_colour_decoder(
|
|
ret.blankStrips, decoderName, dataElement, pairs
|
|
)
|
|
if decoder is not None:
|
|
ret.visualDecoder = decoder
|
|
else:
|
|
print("Bad visualDecoder requested:", decoderName)
|
|
|
|
if "border" in pairs:
|
|
border = pairs["border"]
|
|
if border == "thin":
|
|
ret.border = 0.2
|
|
elif border == "mid":
|
|
ret.border = 0.5
|
|
else:
|
|
ret.border = 1.0
|
|
elif typ == "key":
|
|
ret.colours = pairs.get("colours", ret.colours)
|
|
|
|
return ret
|
|
|
|
def line_is_comment(line):
|
|
"""Returns true if a line starts with #, returns False
|
|
for lines which are None"""
|
|
return line is not None and re.match(r"^\s*#", line) is not None
|
|
|
|
def get_line(f):
|
|
"""Get a line from file f extending that line if it ends in
|
|
'\' and dropping lines that start with '#'s"""
|
|
ret = f.readline()
|
|
|
|
# Discard comment lines
|
|
while line_is_comment(ret):
|
|
ret = f.readline()
|
|
|
|
if ret is not None:
|
|
extend_match = re.match("^(.*)\\\\$", ret)
|
|
|
|
while extend_match is not None:
|
|
new_line = f.readline()
|
|
|
|
if new_line is not None and not line_is_comment(new_line):
|
|
(line_wo_backslash,) = extend_match.groups()
|
|
ret = line_wo_backslash + new_line
|
|
extend_match = re.match("^(.*)\\\\$", ret)
|
|
else:
|
|
extend_match = None
|
|
|
|
return ret
|
|
|
|
# Macros are recursively expanded into name=value pairs
|
|
macros = {}
|
|
|
|
if not os.access(filename, os.R_OK):
|
|
print("Can't open file", filename)
|
|
exit(1)
|
|
else:
|
|
print("Opening file", filename)
|
|
|
|
f = open(filename)
|
|
l = get_line(f)
|
|
picture = []
|
|
blob_char_dict = {}
|
|
|
|
self.unitEvents = {}
|
|
self.clear_events()
|
|
|
|
# Actually parse the file
|
|
in_picture = False
|
|
while l:
|
|
l = parse.remove_trailing_ws(l)
|
|
l = re.sub("#.*", "", l)
|
|
|
|
if re.match(r"^\s*$", l) is not None:
|
|
pass
|
|
elif l == "<<<":
|
|
in_picture = True
|
|
elif l == ">>>":
|
|
in_picture = False
|
|
elif in_picture:
|
|
picture.append(re.sub(r"\s*$", "", l))
|
|
else:
|
|
line_match = re.match(
|
|
r"^([a-zA-Z0-9][a-zA-Z0-9]):\s+([\w.]+)\s*(.*)", l
|
|
)
|
|
macro_match = re.match(r"macro\s+(\w+):(.*)", l)
|
|
|
|
if macro_match is not None:
|
|
name, defn = macro_match.groups()
|
|
macros[name] = parse.parse_pairs_list(defn)
|
|
elif line_match is not None:
|
|
char, unit, pairs = line_match.groups()
|
|
blob = parse_blob_description(
|
|
char, unit, macros, parse.parse_pairs_list(pairs)
|
|
)
|
|
blob_char_dict[char] = blob
|
|
# Setup the events structure
|
|
self.unitEvents[unit] = []
|
|
else:
|
|
print("Problem with Blob line:", l)
|
|
|
|
l = get_line(f)
|
|
|
|
self.blobs = []
|
|
self.add_blob_picture(Point(0, 1), picture, blob_char_dict)
|