| # 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) |