def __init__(self, memorycontent, assertionTriggers, addr2line, pcInitValue=0): # Parameters self.pcoffset = 8 if getSetting("PCbehavior") == "+8" else 0 self.PCSpecialBehavior = getSetting("PCspecialbehavior") self.allowSwitchModeInUserMode = getSetting("allowuserswitchmode") self.maxit = getSetting("runmaxit") self.bkptLastFetch = None self.deactivatedBkpts = [] # Initialize history self.history = History() # Initialize components self.mem = Memory(self.history, memorycontent) self.regs = Registers(self.history) self.pcInitVal = pcInitValue # Initialize decoders self.decoders = { 'BranchOp': BranchOp(), 'DataOp': DataOp(), 'MemOp': MemOp(), 'MultipleMemOp': MultipleMemOp(), 'HalfSignedMemOp': HalfSignedMemOp(), 'SwapOp': SwapOp(), 'PSROp': PSROp(), 'MulOp': MulOp(), 'MulLongOp': MulLongOp(), 'SoftInterruptOp': SoftInterruptOp(), 'NopOp': NopOp() } self.decoderCache = {} # Initialize assertion structures self.assertionCkpts = set(assertionTriggers.keys()) self.assertionData = assertionTriggers self.assertionWhenReturn = set() self.callStack = [] self.addr2line = addr2line # Initialize execution errors buffer self.errorsPending = MultipleErrors() # Initialize interrupt structures self.interruptActive = False # Interrupt trigged at each a*(t-t0) + b cycles self.interruptParams = {'b': 0, 'a': 0, 't0': 0, 'type': "FIQ"} self.lastInterruptCycle = -1 self.stepMode = None self.stepCondition = 0 # Used to stop the simulator after n iterations in run mode self.runIteration = 0 self.history.clear()
def __init__(self, name: str, domain: str, memory_name: str): super(PerformantTestBench, self).__init__(name) # Our Components self.components["application"] = SimpleApplication( "driver", memory_name) self.components["processor"] = PerformantProcessor( "processor", domain, memory_name) self.components["memory"] = Memory("ram", memory_name) # Our Connection connection_params = ConnectionParameters() connection_params.domain = domain.encode("utf-8") connection_params.is_timed = True self.connections["doorbell"] = Connection( self.components["application"].ports["doorbell"], self.components["processor"].ports["doorbell"], connection_params) self.connections["instruction_request"] = Connection( self.components["processor"].ports["instruction_request"], self.components["memory"].ports["requests"], connection_params) self.connections["instruction_response"] = Connection( self.components["processor"].ports["instruction_response"], self.components["memory"].ports["responses"], connection_params) self.connections["data_request"] = Connection( self.components["memory"].ports["requests"], self.components["processor"].ports["data_request"], connection_params) self.connections["data_response"] = Connection( self.components["memory"].ports["responses"], self.components["processor"].ports["data_response"], connection_params)
def __init__(self, num_inputs, num_outputs, controller_size, controller_layers, N, M): """Initialize the NTM. :param num_inputs: External input size. :param num_outputs: External output size. :param controller: :class:`LSTMController` :param memory: :class:`Memory` """ super(NTM, self).__init__() # Save arguments self.num_inputs = num_inputs self.num_outputs = num_outputs self.controller_size = controller_size self.controller_layers = controller_layers self.N = N self.M = M self.memory = Memory(N, M) self.controller = LSTMController(num_inputs + M, controller_size, controller_layers) self.read_head = ReadHead(self.memory, controller_size) self.write_head = WriteHead(self.memory, controller_size) self.heads = [self.read_head, self.write_head] # Initialize the initial previous read values to random biases self.init_r = [torch.randn(1, self.M) * 0.01] self.register_buffer("read_bias", self.init_r[0].data) # Initialize a fully connected layer to produce the actual output: # [controller_output; previous_reads ] -> output self.fc = nn.Linear(self.controller_size + self.M, num_outputs) nn.init.xavier_uniform_(self.fc.weight, gain=1) nn.init.normal_(self.fc.bias, std=0.01)
class NTM(nn.Module): def __init__(self, num_inputs, num_outputs, controller_size, controller_layers, N, M): """Initialize the NTM. :param num_inputs: External input size. :param num_outputs: External output size. :param controller: :class:`LSTMController` :param memory: :class:`Memory` """ super(NTM, self).__init__() # Save arguments self.num_inputs = num_inputs self.num_outputs = num_outputs self.controller_size = controller_size self.controller_layers = controller_layers self.N = N self.M = M self.memory = Memory(N, M) self.controller = LSTMController(num_inputs + M, controller_size, controller_layers) self.read_head = ReadHead(self.memory, controller_size) self.write_head = WriteHead(self.memory, controller_size) self.heads = [self.read_head, self.write_head] # Initialize the initial previous read values to random biases self.init_r = [torch.randn(1, self.M) * 0.01] self.register_buffer("read_bias", self.init_r[0].data) # Initialize a fully connected layer to produce the actual output: # [controller_output; previous_reads ] -> output self.fc = nn.Linear(self.controller_size + self.M, num_outputs) nn.init.xavier_uniform_(self.fc.weight, gain=1) nn.init.normal_(self.fc.bias, std=0.01) def create_new_state(self, batch_size): init_r = [r.clone().repeat(batch_size, 1) for r in self.init_r] controller_state = self.controller.create_new_state(batch_size) heads_state = [ torch.zeros(batch_size, self.N), torch.zeros(batch_size, self.N) ] return init_r, controller_state, heads_state def initialize(self, batch_size): """Initializing the state.""" self.batch_size = batch_size self.memory.reset(batch_size) self.previous_state = self.create_new_state(batch_size) def forward(self, x=None): """NTM forward function. :param x: input vector (batch_size x num_inputs) :param prev_state: The previous state of the NTM """ if x is None: x = torch.zeros(self.batch_size, self.num_inputs) # Unpack the previous state prev_reads, prev_controller_state, prev_heads_states = self.previous_state # Use the controller to get an embeddings inp = torch.cat([x] + prev_reads, dim=1) controller_outp, controller_state = self.controller( inp, prev_controller_state) # Read/Write from the list of heads reads = [] heads_states = [] for head, prev_head_state in zip(self.heads, prev_heads_states): if head.head_type is "read": r, head_state = head(controller_outp, prev_head_state) reads += [r] else: head_state = head(controller_outp, prev_head_state) heads_states += [head_state] # Generate Output inp2 = torch.cat([controller_outp] + reads, dim=1) o = torch.sigmoid(self.fc(inp2)) # Pack the current state self.previous_state = (reads, controller_state, heads_states) return o, self.previous_state def calculate_num_params(self): """Returns the total number of parameters.""" num_params = 0 for p in self.parameters(): num_params += p.data.view(-1).size(0) return num_params
class Simulator: """ Main simulator class. None of its method should be called directly by the UI, everything should pass through bytecodeinterpreter class. """ PC = 15 # Helpful shorthand to get a reference on PC def __init__(self, memorycontent, assertionTriggers, addr2line, pcInitValue=0): # Parameters self.pcoffset = 8 if getSetting("PCbehavior") == "+8" else 0 self.PCSpecialBehavior = getSetting("PCspecialbehavior") self.allowSwitchModeInUserMode = getSetting("allowuserswitchmode") self.maxit = getSetting("runmaxit") self.bkptLastFetch = None self.deactivatedBkpts = [] # Initialize history self.history = History() # Initialize components self.mem = Memory(self.history, memorycontent) self.regs = Registers(self.history) self.pcInitVal = pcInitValue # Initialize decoders self.decoders = { 'BranchOp': BranchOp(), 'DataOp': DataOp(), 'MemOp': MemOp(), 'MultipleMemOp': MultipleMemOp(), 'HalfSignedMemOp': HalfSignedMemOp(), 'SwapOp': SwapOp(), 'PSROp': PSROp(), 'MulOp': MulOp(), 'MulLongOp': MulLongOp(), 'SoftInterruptOp': SoftInterruptOp(), 'NopOp': NopOp() } self.decoderCache = {} # Initialize assertion structures self.assertionCkpts = set(assertionTriggers.keys()) self.assertionData = assertionTriggers self.assertionWhenReturn = set() self.callStack = [] self.addr2line = addr2line # Initialize execution errors buffer self.errorsPending = MultipleErrors() # Initialize interrupt structures self.interruptActive = False # Interrupt trigged at each a*(t-t0) + b cycles self.interruptParams = {'b': 0, 'a': 0, 't0': 0, 'type': "FIQ"} self.lastInterruptCycle = -1 self.stepMode = None self.stepCondition = 0 # Used to stop the simulator after n iterations in run mode self.runIteration = 0 self.history.clear() def reset(self): self.history.clear() self.regs.banks['User'][15].val = self.pcInitVal + self.pcoffset self.fetchAndDecode() self.explainInstruction() def getContext(self): context = { "regs": self.regs.getContext(), "mem": self.mem.getContext() } return context def setStepCondition(self, stepMode): assert stepMode in ("into", "out", "forward", "run") self.stepMode = stepMode self.stepCondition = 1 self.runIteration = self.history.cyclesCount def isStepDone(self): maxCyclesReached = self.history.cyclesCount - self.runIteration >= self.maxit if self.stepMode == "forward": if self.stepCondition == 2: # The instruction was a function call # Now the step forward becomes a step out self.stepMode = "out" self.stepCondition = 1 return False else: return True if self.stepMode == "out": return self.stepCondition == 0 or maxCyclesReached if self.stepMode == "run": return maxCyclesReached # We are doing a step into, we always stop return True def loop(self): """ Loop until the stopping criterion is met. Returns the aggregated list of changes since the beginning of the simulation loop. Stopping criterion can be set using `setStepCondition`. """ self.history.setCheckpoint() for decoder in self.decoders.values(): decoder.resetExecCounters() self.nextInstr() # We always execute at least one instruction while not self.isStepDone( ): # We repeat until the stopping criterion is met self.nextInstr() self.explainInstruction( ) # We only have to explain the last instruction executed before we stop def stepBack(self, count=1): for c in range(count): self.history.stepBack() self.fetchAndDecode(forceExplain=True) self.bkptLastFetch = None def executionStats(self): """ Return a dictionnary with the number of times each instruction type was executed in the last execution run. The types are: - "data" (includes all arithmetic and logic operations except multiply) - "mem" (includes all _single_ memory accesses including byte, half or word access and swap) - "multiplemem" (all _multiple_ memory accesses: LDM, STM, POP, and PUSH) - "branch" (all branches, B/BL/BX alike) - "multiply" (multiply and multiply long operations) - "softinterrupt" (self explanatory) - "psr" (CPSR/SPRS <-> register transfers) - "nop" (NOP instructions) These keys are associated to a 2-integers tuple. The first value is the number of times this kind of instruction was executed, the second the number of times if _would_ have been executed except for the condition field (e.g. the condition was not met). """ memExec = self.decoders['MemOp'].execCounters halfMemExec = self.decoders['HalfSignedMemOp'].execCounters swapExec = self.decoders['SwapOp'].execCounters multiplyExec = self.decoders['MulOp'].execCounters multiplyLongExec = self.decoders['MulLongOp'].execCounters return { "data": self.decoders['DataOp'].execCounters, "mem": (memExec[0] + halfMemExec[0] + swapExec[0], memExec[1] + halfMemExec[1] + swapExec[1]), "multiplemem": self.decoders['MultipleMemOp'].execCounters, "branch": self.decoders['BranchOp'].execCounters, "multiply": (multiplyExec[0] + multiplyLongExec[0], multiplyExec[1] + multiplyLongExec[1]), "softinterrupt": self.decoders['SoftInterruptOp'].execCounters, "psr": self.decoders['PSROp'].execCounters, "nop": self.decoders['NopOp'].execCounters } def fetchAndDecode(self, forceExplain=False): # Check if PC is valid (multiple of 4) if (self.regs[15] - self.pcoffset) % 4 != 0: self.fetchedInstr = None self.errorsPending.append( 'register', "Erreur : la valeur de PC ({}) est invalide (ce doit être un multiple de 4)!" .format(hex(self.regs[15]))) else: try: # Retrieve instruction from memory self.fetchedInstr = bytes( self.mem.get(self.regs[15] - self.pcoffset, execMode=True)) except Breakpoint as bp: # We hit a breakpoint # Get memory instruction again, without trigger breakpoint self.fetchedInstr = bytes( self.mem.get(self.regs[15] - self.pcoffset, mayTriggerBkpt=False)) self.bkptLastFetch = bp except ComponentException as err: # Execution error self.errorsPending.append(err.cmp, err.text) # There is no instruction to this address self.fetchedInstr = None self.bytecodeToInstr() if forceExplain or self.isStepDone(): self.explainInstruction() def bytecodeToInstr(self): """ Decode an instruction from its bytecode representation. See ARM Instruction set documentation for more information (Fig 4.1, and also Sec. A5.1 of ARM-v7 Architecture Reference Manual). We use the following decoding scheme, designed to ensure that avoid any instruction representation clash and also to reduce the number of conditions having to be checked in total. Each number inside [ ] represents a bit position. The usual binary ops symbols apply. Note that NOP was backported from ARMv7, and that ARMv4 coprocessor instructions are not supported. Also, we do not explicitely check for undefined instructions (in the ARM terminology, they are more _unpredictable_ than undefined). Decoded instructions are also cached to improve performance. Bytecode input (4 bytes) | | v [~26 & ~27]---------------------------------------------------------v | | | FALSE | TRUE | | v v [26 ]--------------------------------v | | | | | FALSE | TRUE | | | | v v v [25 ]----------------v [27 ]-------------v | | | | | | | FALSE | TRUE | FALSE | TRUE | | | | | | v v v v v LDM / STM Branch (B) LDR / STR SWI / SVC | (also undefined | if [4]) | v--------------------------------------------------------------< | v [4 & 7 & ~25]--------------------------------------------v | | | FALSE | TRUE | | v v [~20 & ~23 & 24]----------v [5 | 6]--------v | | | | TRUE | FALSE | TRUE | FALSE v | | | LDRH/SH/SB v v v DATA OP [18 & 21]-----------v [24 ]---------v (MOV, ADD, etc.) | | | | TRUE | FALSE | TRUE | FALSE v | | | SWP v v v v----------------[19 ] BX [23 ]---------v | | | | TRUE | TRUE | FALSE | FALSE v | | | Multiply v v v long MSR / MRS NOP Multiply """ if not self.fetchedInstr: # Undefined instruction self.currentInstr = None return # Assumes that the instruction to decode is in self.fetchedInstr instrInt = struct.unpack("<I", self.fetchedInstr)[0] if instrInt in self.decoderCache: self.currentInstr = self.decoderCache[instrInt][0] self.currentInstr.setBytecode(instrInt) self.currentInstr.restoreState(self.decoderCache[instrInt][1]) return if not (instrInt >> 26 & 3): if instrInt >> 4 & 9 == 9 and not (instrInt >> 25 & 1): if instrInt >> 5 & 3: self.currentInstr = self.decoders['HalfSignedMemOp'] elif instrInt >> 24 & 1: self.currentInstr = self.decoders['SwapOp'] elif instrInt >> 23 & 1: self.currentInstr = self.decoders['MulLongOp'] else: self.currentInstr = self.decoders['MulOp'] elif instrInt >> 24 & 1 and not (instrInt >> 20 & 9): if instrInt >> 18 & 9 == 9: self.currentInstr = self.decoders['BranchOp'] elif instrInt >> 19 & 1: self.currentInstr = self.decoders['PSROp'] else: self.currentInstr = self.decoders['NopOp'] else: self.currentInstr = self.decoders['DataOp'] elif instrInt >> 26 & 1: if instrInt >> 27 & 1: self.currentInstr = self.decoders['SoftInterruptOp'] else: # Could also check for [4], which is an undefined space in the instruction set self.currentInstr = self.decoders['MemOp'] elif instrInt >> 25 & 1: self.currentInstr = self.decoders['BranchOp'] else: self.currentInstr = self.decoders['MultipleMemOp'] if self.currentInstr is not None: self.currentInstr.setBytecode(instrInt) try: self.currentInstr.decode() # Once decoded, we add the instruction to the cache self.decoderCache[instrInt] = (self.currentInstr, self.currentInstr.saveState()) if len(self.decoderCache) > 2000: # Fail-safe, we should never get there with programs < 2000 lines, but just in case, # we do not want to bust the RAM with our cache self.decoderCache = {} except ExecutionException as err: # Invalid instruction self.currentInstr = None self.errorsPending.append('execution', err.text) def explainInstruction(self): if not self.currentInstr: # Undefined instruction self.disassemblyInfo = (["highlightread", []], [ "highlightwrite", [] ], ["nextline", None], ["disassembly", "Information indisponible"]) return disassembly, description = self.currentInstr.explain(self) dis = '<div id="disassembly_instruction">{}</div>\n<div id="disassembly_description">{}</div>\n'.format( disassembly, description) if self.currentInstr.nextAddressToExecute != -1: self.disassemblyInfo = ([ "highlightread", list(self.currentInstr.affectedRegs[0] | self.currentInstr.affectedMem[0]) ], [ "highlightwrite", list(self.currentInstr.affectedRegs[1] | self.currentInstr.affectedMem[1]) ], ["nextline", self.currentInstr.nextAddressToExecute], ["disassembly", dis]) else: self.disassemblyInfo = ([ "highlightread", list(self.currentInstr.affectedRegs[0] | self.currentInstr.affectedMem[0]) ], [ "highlightwrite", list(self.currentInstr.affectedRegs[1] | self.currentInstr.affectedMem[1]) ], ["disassembly", dis]) def execAssert(self, assertionsList, mode): for assertionInfo in assertionsList: assertionType = assertionInfo[0] if assertionType != mode: continue assertionLine = assertionInfo[1] assertionInfo = assertionInfo[2].split(",") strError = "" try: for info in assertionInfo: info = info.strip() if "=" not in info: # Bad syntax, we skip continue target, value = info.split("=") if target[0] == "R": # Register reg = int(target[1:]) try: val = int(value, base=0) & 0xFFFFFFFF except ValueError: # If this is a decimal with leading zeros, base=0 will crash val = int(value, base=10) & 0xFFFFFFFF self.regs.deactivateBreakpoints() valreg = self.regs[reg] self.regs.reactivateBreakpoints() if valreg != val: strError += "Erreur : {} devrait valoir {}, mais il vaut {}\n".format( target, val, valreg) elif target[:2] == "0x": # Memory addr = int(target, base=16) try: val = int(value, base=0) except ValueError: # If this is a decimal with leading zeros, base=0 will crash val = int(value, base=10) formatStruct = "<B" if not 0 <= int(val) < 255: val &= 0xFFFFFFFF formatStruct = "<I" valmem = self.mem.get( addr, mayTriggerBkpt=False, size=4 if formatStruct == "<I" else 1) valmem = struct.unpack(formatStruct, valmem)[0] if valmem != val: strError += "Erreur : l'adresse mémoire {} devrait contenir {}, mais elle contient {}\n"\ .format(target, val, valmem) elif len(target) == 1 and target in self.regs.flag2index: # Flag expectedVal = value != '0' actualVal = self.regs.__getattribute__(target) if actualVal != expectedVal: strError += "Erreur : le drapeau {} devrait signaler {}, mais il signale {}\n"\ .format(target, expectedVal, actualVal) else: # Assert type unknown strError += "Assertion inconnue : ({}, {})!".format( target, val) if len(strError) > 0: self.errorsPending.append("assert", strError, assertionLine) except ComponentException as ex: self.errorsPending.append(ex.cmp, ex.text, assertionLine) def nextInstr(self, forceExplain=False): if self.currentInstr is None: # The current instruction has not be retrieved or decoded (because it was an illegal access) # We raise the last error to explain the illegal access raise self.errorsPending isFirstInst = self.runIteration == self.history.cyclesCount # One more cycle to do! self.history.newCycle() # Clear previous errors self.errorsPending.clear() keeppc = self.regs[15] - self.pcoffset currentCallStackLen = len(self.callStack) if self.stepMode in ("out", "run", "forward") and not isFirstInst: if self.bkptLastFetch: # We hit a breakpoint on the last decoded instruction err = self.bkptLastFetch self.bkptLastFetch = None self.history.restartCycle() raise err try: self.currentInstr.execute(self) except Breakpoint as bp: # We hit a breakpoint on READ/WRITE # We temporary disable the raised breakpoint self.deactivatedBkpts.append(bp) self._toggleBreakpoint(bp) self.history.restartCycle() raise bp except ComponentException as err: self.errorsPending.append(err.cmp, err.text, self.getCurrentLine()) except ExecutionException as err: self.errorsPending.append('execution', err.text, self.getCurrentLine()) else: # In stepIn mode or this is the first step to be execute in this mode. # Breakpoints are temporary deactivate try: self.deactivateAllBreakpoints() self.currentInstr.execute(self) except ComponentException as err: self.errorsPending.append(err.cmp, err.text, self.getCurrentLine()) except ExecutionException as err: self.errorsPending.append('execution', err.text, self.getCurrentLine()) self.reactivateAllBreakpoints() # After instruction execution, restore all breakpoints for bp in self.deactivatedBkpts: self._toggleBreakpoint(bp) self.deactivatedBkpts = [] if self.currentInstr.pcmodified: # If PC was modified, we simulate the prefetch by adding 8 immediately to it self.regs[15] += self.pcoffset else: self.regs[15] += 4 # PC = PC + 4 newpc = self.regs[15] - self.pcoffset if keeppc in self.assertionCkpts and not self.currentInstr.pcmodified: # We check if we've hit an post-assertion checkpoint self.execAssert(self.assertionData[keeppc], 'AFTER') elif currentCallStackLen > len(self.callStack): # We have branched out of a function # If an assertion was following a BL and we exited a function, we want to execute it now! if len(self.callStack) in self.assertionWhenReturn and ( newpc - 4) in self.assertionCkpts: self.execAssert(self.assertionData[newpc - 4], 'AFTER') self.assertionWhenReturn.remove(len(self.callStack)) elif currentCallStackLen < len(self.callStack): # We have branched in a function # We want to remember that we want to assert something when we return if keeppc in self.assertionCkpts: self.assertionWhenReturn.add(currentCallStackLen) if newpc in self.assertionCkpts: # We check if we've hit an pre-assertion checkpoint (for the next instruction) self.execAssert(self.assertionData[newpc], 'BEFORE') # We look for interrupts # The current instruction is always finished before the interrupt takes on # TODO Handle special cases for LDM and STM if self.interruptActive and self.history.cyclesCount >= (self.interruptParams['t0'] + self.interruptParams['b']) \ and (self.history.cyclesCount - 1 - self.interruptParams['t0'] - self.interruptParams['b']) % self.interruptParams['a'] == 0: if (self.interruptParams['type'] == "FIQ" and not self.regs.FIQ or self.interruptParams['type'] == "IRQ" and not self.regs.IRQ and self.regs.mode != 'FIQ'): # Is the interrupt masked? # Interruption! # We enter it (the entry point is 0x18 for IRQ and 0x1C for FIQ) savedCPSR = self.regs.CPSR # Keep CPSR before changing processor mode self.regs.mode = self.interruptParams[ 'type'] # Set the register bank and processor mode self.regs.SPSR = savedCPSR # Save the CPSR in the current SPSR self.regs.IRQ = True # IRQ are always disabled when we enter an interrupt if self.interruptParams[ 'type'] == "FIQ": # If we enter a FIQ interrupt, self.regs.FIQ = True # then we disable also FIQ interrupts self.regs[14] = self.regs[ 15] - 4 # Save PC in LR (on the FIQ or IRQ bank) self.regs[15] = self.pcoffset + ( 0x18 if self.interruptParams['type'] == "IRQ" else 0x1C ) # Set PC to enter the interrupt # We fetch and decode the next instruction self.fetchAndDecode(forceExplain) if self.errorsPending: raise self.errorsPending def deactivateAllBreakpoints(self): # Without removing them, do not trig on breakpoint until `reactivateAllBreakpoints` # is called. Useful to temporary disable breakpoints of Memory and Registers self.regs.deactivateBreakpoints() self.mem.deactivateBreakpoints() def reactivateAllBreakpoints(self): # See `deactivateAllBreakpoints` self.regs.reactivateBreakpoints() self.mem.reactivateBreakpoints() def getCurrentLine(self): self.regs.deactivateBreakpoints() pc = self.regs[15] self.regs.reactivateBreakpoints() pc -= 8 if getSetting("PCbehavior") == "+8" else 0 if pc in self.addr2line and len(self.addr2line[pc]) > 0: return self.addr2line[pc][-1] else: return None def _toggleBreakpoint(self, bkptException): if bkptException.cmp == "memory": self.mem.toggleBreakpoint(bkptException.info, bkptException.mode) elif bkptException.cmp == "register": self.regs.toggleBreakpointOnRegister(bkptException.info[0], bkptException.info[1], bkptException.mode) elif bkptException.cmp == "flags": self.regs.toggleBreakpointOnFlag(bkptException.info, bkptException.mode)