1110 lines
38 KiB
Python
1110 lines
38 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.
|
||
|
#
|
||
|
# Authors: Andrew Bardsley
|
||
|
|
||
|
import parse
|
||
|
import colours
|
||
|
from colours import unknownColour
|
||
|
from point import Point
|
||
|
import re
|
||
|
import blobs
|
||
|
from time import time as wall_time
|
||
|
import os
|
||
|
|
||
|
id_parts = "TSPLFE"
|
||
|
|
||
|
all_ids = set(id_parts)
|
||
|
no_ids = set([])
|
||
|
|
||
|
class BlobDataSelect(object):
|
||
|
"""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(object):
|
||
|
"""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('^(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('^(\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 = map(int, re.split('/', string))
|
||
|
return self
|
||
|
|
||
|
def to_striped_block(self, select):
|
||
|
return 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(object):
|
||
|
"""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 xrange(0, strip_elems)]
|
||
|
for j in xrange(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 = 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(object):
|
||
|
"""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(Inst,self).__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 = '0x%x->0x%x' % (self.addr, self.nextAddr)
|
||
|
else:
|
||
|
addrStr = '0x%x' % self.addr
|
||
|
ret = [addrStr, self.disassembly]
|
||
|
for name, value in self.pairs.iteritems():
|
||
|
ret.append("%s=%s" % (name, str(value)))
|
||
|
return ret
|
||
|
|
||
|
class InstFault(IdedObj):
|
||
|
"""A fault instruction"""
|
||
|
def __init__(self, id, fault, addr, pairs={}):
|
||
|
super(InstFault,self).__init__(id, pairs)
|
||
|
self.fault = fault
|
||
|
self.addr = addr
|
||
|
|
||
|
def table_line(self):
|
||
|
ret = ["0x%x" % self.addr, 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(Line,self).__init__(id, pairs)
|
||
|
self.vaddr = vaddr
|
||
|
self.paddr = paddr
|
||
|
self.size = size
|
||
|
|
||
|
def table_line(self):
|
||
|
ret = ["0x%x/0x%x" % (self.vaddr, self.paddr), "%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(LineFault,self).__init__(id, pairs)
|
||
|
self.vaddr = vaddr
|
||
|
self.fault = fault
|
||
|
|
||
|
def table_line(self):
|
||
|
ret = ["0x%x" % self.vaddr, self.fault]
|
||
|
for name, value in self.pairs:
|
||
|
ret.append("%s=%s", name, str(value))
|
||
|
return ret
|
||
|
|
||
|
class BlobEvent(object):
|
||
|
"""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)
|
||
|
map(find_inst, blocks)
|
||
|
return sorted(ret)
|
||
|
|
||
|
class BlobModel(object):
|
||
|
"""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.iteritems():
|
||
|
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.itervalues():
|
||
|
for event in unitEvents:
|
||
|
times[event.time] = 1
|
||
|
self.times = 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('^\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(
|
||
|
'^\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 + '\.?(.*)$',
|
||
|
'\\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('^\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("^\s*$", l) is not None:
|
||
|
pass
|
||
|
elif l == '<<<':
|
||
|
in_picture = True
|
||
|
elif l == '>>>':
|
||
|
in_picture = False
|
||
|
elif in_picture:
|
||
|
picture.append(re.sub('\s*$', '', l))
|
||
|
else:
|
||
|
line_match = re.match(
|
||
|
'^([a-zA-Z0-9][a-zA-Z0-9]):\s+([\w.]+)\s*(.*)', l)
|
||
|
macro_match = re.match('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)
|