| # 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. | |
| from . import parse | |
| from . import colours | |
| from .colours import unknownColour | |
| from .point import Point | |
| import re | |
| from . 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 = 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(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 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(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.items(): | |
| 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) | |
| list(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.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('^\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) |