Simplify timekeeping
[awlsim.git] / awlsim / core / cpu.py
1 # -*- coding: utf-8 -*-
2 #
3 # AWL simulator - CPU
4 #
5 # Copyright 2012-2014 Michael Buesch <m@bues.ch>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #
21
22 from __future__ import division, absolute_import, print_function, unicode_literals
23 from awlsim.core.compat import *
24
25 import time
26 import datetime
27 import random
28
29 from awlsim.core.cpuspecs import *
30 from awlsim.core.parser import *
31 from awlsim.core.symbolparser import *
32 from awlsim.core.datatypes import *
33 from awlsim.core.instructions.all_insns import *
34 from awlsim.core.systemblocks.system_sfb import *
35 from awlsim.core.systemblocks.system_sfc import *
36 from awlsim.core.operators import *
37 from awlsim.core.insntrans import *
38 from awlsim.core.optrans import *
39 from awlsim.core.blocks import *
40 from awlsim.core.datablocks import *
41 from awlsim.core.statusword import *
42 from awlsim.core.labels import *
43 from awlsim.core.timers import *
44 from awlsim.core.counters import *
45 from awlsim.core.callstack import *
46 from awlsim.core.obtemp import *
47 from awlsim.core.util import *
48
49
50 class ParenStackElem(object):
51         "Parenthesis stack element"
52
53         def __init__(self, cpu, insnType, statusWord):
54                 self.cpu = cpu
55                 self.insnType = insnType
56                 self.NER = statusWord.NER
57                 self.VKE = statusWord.VKE
58                 self.OR = statusWord.OR
59
60         def __repr__(self):
61                 mnemonics = self.cpu.specs.getMnemonics()
62                 type2name = {
63                         S7CPUSpecs.MNEMONICS_EN : AwlInsn.type2name_english,
64                         S7CPUSpecs.MNEMONICS_DE : AwlInsn.type2name_german,
65                 }[mnemonics]
66                 return '(insn="%s" VKE=%s OR=%d)' %\
67                         (type2name[self.insnType],
68                          self.VKE, self.OR)
69
70 class McrStackElem(object):
71         "MCR stack element"
72
73         def __init__(self, statusWord):
74                 self.VKE = statusWord.VKE
75
76         def __bool__(self):
77                 return bool(self.VKE)
78
79         __nonzero__ = __bool__
80
81 class S7CPU(object):
82         "STEP 7 CPU"
83
84         def __init__(self, sim):
85                 self.sim = sim
86                 self.specs = S7CPUSpecs(self)
87                 self.setCycleTimeLimit(5.0)
88                 self.setCycleExitCallback(None)
89                 self.setBlockExitCallback(None)
90                 self.setPostInsnCallback(None)
91                 self.setPeripheralReadCallback(None)
92                 self.setPeripheralWriteCallback(None)
93                 self.setScreenUpdateCallback(None)
94                 self.reset()
95                 self.enableExtendedInsns(False)
96                 self.enableObTempPresets(False)
97
98         def enableObTempPresets(self, en=True):
99                 self.__obTempPresetsEnabled = bool(en)
100
101         def obTempPresetsEnabled(self):
102                 return self.__obTempPresetsEnabled
103
104         def enableExtendedInsns(self, en=True):
105                 self.__extendedInsnsEnabled = bool(en)
106
107         def extendedInsnsEnabled(self):
108                 return self.__extendedInsnsEnabled
109
110         def setCycleTimeLimit(self, newLimit):
111                 self.cycleTimeLimit = float(newLimit)
112
113         def __detectMnemonics(self, parseTree):
114                 specs = self.getSpecs()
115                 if specs.getConfiguredMnemonics() != S7CPUSpecs.MNEMONICS_AUTO:
116                         return
117                 codeBlocks = list(parseTree.obs.values())
118                 codeBlocks.extend(parseTree.fbs.values())
119                 codeBlocks.extend(parseTree.fcs.values())
120                 errorCounts = {
121                         S7CPUSpecs.MNEMONICS_EN         : 0,
122                         S7CPUSpecs.MNEMONICS_DE         : 0,
123                 }
124                 detected = None
125                 for mnemonics in (S7CPUSpecs.MNEMONICS_EN,
126                                   S7CPUSpecs.MNEMONICS_DE):
127                         for block in codeBlocks:
128                                 for rawInsn in block.insns:
129                                         ret = AwlInsnTranslator.name2type(rawInsn.getName(),
130                                                                           mnemonics)
131                                         if ret is None:
132                                                 errorCounts[mnemonics] += 1
133                                         try:
134                                                 optrans = AwlOpTranslator(None, mnemonics)
135                                                 optrans.translateFromRawInsn(rawInsn)
136                                         except AwlSimError:
137                                                 errorCounts[mnemonics] += 1
138                         if errorCounts[mnemonics] == 0:
139                                 # No error. Use these mnemonics.
140                                 detected = mnemonics
141                 if detected is None:
142                         # Select the mnemonics with the lower error count.
143                         if errorCounts[S7CPUSpecs.MNEMONICS_EN] <= errorCounts[S7CPUSpecs.MNEMONICS_DE]:
144                                 detected = S7CPUSpecs.MNEMONICS_EN
145                         else:
146                                 detected = S7CPUSpecs.MNEMONICS_DE
147                 if specs.getMnemonics() != S7CPUSpecs.MNEMONICS_AUTO:
148                         # Autodetected mnemonics were already set before
149                         if specs.getMnemonics() != detected:
150                                 raise AwlSimError("Cannot mix multiple AWL files with "\
151                                         "distinct mnemonics. This error may be caused by "\
152                                         "incorrect autodetection. "\
153                                         "Force mnemonics to EN or DE to avoid this error.")
154                 specs.setDetectedMnemonics(detected)
155
156         def __translateInsn(self, rawInsn, ip):
157                 ex = None
158                 try:
159                         insn = AwlInsnTranslator.fromRawInsn(self, rawInsn)
160                         insn.setIP(ip)
161                 except AwlSimError as e:
162                         if e.getRawInsn() is None:
163                                 e.setRawInsn(rawInsn)
164                         raise e
165                 return insn
166
167         def __translateInsns(self, rawInsns):
168                 insns = []
169                 # Translate raw instructions to simulator instructions
170                 for ip, rawInsn in enumerate(rawInsns):
171                         insns.append(self.__translateInsn(rawInsn, ip))
172                 # If the last instruction is not BE or BEA, add an implicit BE
173                 if not insns or insns[-1].type not in (AwlInsn.TYPE_BE,
174                                                        AwlInsn.TYPE_BEA):
175                         insns.append(AwlInsn_BE(cpu = self, rawInsn = None))
176                 return insns
177
178         def __translateInterfaceField(self, rawVar):
179                 dtype = AwlDataType.makeByName(rawVar.typeTokens, rawVar.dimensions)
180                 assert(len(rawVar.idents) == 1) #TODO no structs, yet
181                 field = BlockInterfaceField(name = rawVar.idents[0].name,
182                                             dataType = dtype)
183                 return field
184
185         def __translateCodeBlock(self, rawBlock, blockClass):
186                 insns = self.__translateInsns(rawBlock.insns)
187                 block = blockClass(insns, rawBlock.index)
188                 for rawVar in rawBlock.vars_in:
189                         block.interface.addField_IN(self.__translateInterfaceField(rawVar))
190                 for rawVar in rawBlock.vars_out:
191                         block.interface.addField_OUT(self.__translateInterfaceField(rawVar))
192                 if rawBlock.retTypeTokens:
193                         # ARRAY is not supported for RET_VAL. So make non-array dtype.
194                         dtype = AwlDataType.makeByName(rawBlock.retTypeTokens)
195                         if dtype.type != AwlDataType.TYPE_VOID:
196                                 # Ok, we have a RET_VAL.
197                                 field = BlockInterfaceField(name = "RET_VAL",
198                                                             dataType = dtype)
199                                 block.interface.addField_OUT(field)
200                 for rawVar in rawBlock.vars_inout:
201                         block.interface.addField_INOUT(self.__translateInterfaceField(rawVar))
202                 for rawVar in rawBlock.vars_static:
203                         block.interface.addField_STAT(self.__translateInterfaceField(rawVar))
204                 for rawVar in rawBlock.vars_temp:
205                         block.interface.addField_TEMP(self.__translateInterfaceField(rawVar))
206                 block.interface.buildDataStructure()
207                 return block
208
209         # Initialize a DB (global or instance) data field from a raw data-init.
210         def __initDBField(self, db, dataType, rawDataInit):
211                 if dataType.type == AwlDataType.TYPE_ARRAY:
212                         index = dataType.arrayIndicesCollapse(*rawDataInit.idents[-1].indices)
213                 else:
214                         index = None
215                 value = dataType.parseMatchingImmediate(rawDataInit.valueTokens)
216                 db.structInstance.setFieldDataByName(rawDataInit.idents[-1].name,
217                                                      index, value)
218
219         def __translateGlobalDB(self, rawDB):
220                 db = DB(rawDB.index, None)
221                 # Create the data structure fields
222                 for field in rawDB.fields:
223                         assert(len(field.idents) == 1) #TODO no structs, yet
224                         if not rawDB.getFieldInit(field):
225                                 raise AwlSimError(
226                                         "DB %d declares field '%s', "
227                                         "but does not initialize." %\
228                                         (rawDB.index, field.idents[0].name))
229                         dtype = AwlDataType.makeByName(field.typeTokens,
230                                                        field.dimensions)
231                         db.struct.addFieldNaturallyAligned(field.idents[0].name,
232                                                            dtype)
233                 # Allocate the data structure fields
234                 db.allocate()
235                 # Initialize the data structure fields
236                 for field, init in rawDB.allFieldInits():
237                         if not field:
238                                 raise AwlSimError(
239                                         "DB %d assigns field '%s', "
240                                         "but does not declare it." %\
241                                         (rawDB.index, init.getIdentString()))
242                         assert(len(field.idents) == 1 and len(init.idents) == 1) #TODO no structs, yet
243                         dtype = AwlDataType.makeByName(field.typeTokens,
244                                                        field.dimensions)
245                         self.__initDBField(db, dtype, init)
246                 return db
247
248         def __translateInstanceDB(self, rawDB):
249                 fbStr = "SFB" if rawDB.fb.isSFB else "FB"
250                 try:
251                         if rawDB.fb.isSFB:
252                                 fb = self.sfbs[rawDB.fb.fbNumber]
253                         else:
254                                 fb = self.fbs[rawDB.fb.fbNumber]
255                 except KeyError:
256                         raise AwlSimError("Instance DB %d references %s %d, "
257                                 "but %s %d does not exist." %\
258                                 (rawDB.index,
259                                  fbStr, rawDB.fb.fbNumber,
260                                  fbStr, rawDB.fb.fbNumber))
261                 db = DB(rawDB.index, fb)
262                 interface = fb.interface
263                 # Sanity checks
264                 if rawDB.fields:
265                         raise AwlSimError("DB %d is an "
266                                 "instance DB, but it also "
267                                 "declares a data structure." %\
268                                 rawDB.index)
269                 # Allocate the data structure fields
270                 db.allocate()
271                 # Initialize the data structure fields
272                 for init in rawDB.fieldInits:
273                         assert(len(init.idents) == 1) #TODO no structs, yet
274                         dtype = interface.getFieldByName(init.idents[-1].name).dataType
275                         self.__initDBField(db, dtype, init)
276                 return db
277
278         def __translateDB(self, rawDB):
279                 if rawDB.index < 0:
280                         raise AwlSimError("DB number %d is invalid" % rawDB.index)
281                 if rawDB.isInstanceDB():
282                         return self.__translateInstanceDB(rawDB)
283                 return self.__translateGlobalDB(rawDB)
284
285         # Translate classic symbols ("abc")
286         def __resolveClassicSym(self, block, insn, oper):
287                 if oper.type == AwlOperator.SYMBOLIC:
288                         symbol = self.symbolTable.findOneByName(oper.value.varName)
289                         if not symbol:
290                                 raise AwlSimError("Symbol \"%s\" not found in "
291                                         "symbol table." % oper.value.varName,
292                                         insn = insn)
293                         oper = symbol.operator
294                 return oper
295
296         # Translate symbolic OB/FB/FC/DB block name
297         def __resolveBlockName(self, blockTypeId, blockName):
298                 if isString(blockName):
299                         symbol = self.symbolTable.findOneByName(blockName)
300                         if not symbol:
301                                 raise AwlSimError("Symbolic block name \"%s\" "
302                                         "not found in symbol table." % blockName)
303                         if symbol.type.type != blockTypeId:
304                                 raise AwlSimError("Symbolic block name \"%s\" "
305                                         "has an invalid type." % blockName)
306                         return symbol.operator.value.byteOffset
307                 return blockName
308
309         # Translate local symbols (#abc or P##abc)
310         # If pointer is false, try to resolve #abc.
311         # If pointer is true, try to resolve P##abc.
312         def resolveNamedLocal(self, block, insn, oper, pointer=False):
313                 if pointer:
314                         if oper.type != AwlOperator.NAMED_LOCAL_PTR:
315                                 return oper
316                 else:
317                         if oper.type != AwlOperator.NAMED_LOCAL:
318                                 return oper
319
320                 # Get the interface field for this variable
321                 field = block.interface.getFieldByName(oper.value.varName)
322
323                 # Sanity checks
324                 if field.dataType.type == AwlDataType.TYPE_ARRAY:
325                         if not oper.value.indices:
326                                 raise AwlSimError("Cannot address array #%s "
327                                         "without subscript list." %\
328                                         oper.value.varName)
329                 else:
330                         if oper.value.indices:
331                                 raise AwlSimError("Trying to subscript array, "
332                                         "but #%s is not an array." %\
333                                         oper.value.varName)
334
335                 if block.interface.hasInstanceDB or\
336                    field.fieldType == BlockInterfaceField.FTYPE_TEMP:
337                         # This is an FB or a TEMP access. Translate the operator
338                         # to a DI/TEMP access.
339                         newOper = block.interface.getOperatorForField(oper.value.varName,
340                                                                       oper.value.indices,
341                                                                       pointer)
342                         newOper.setInsn(oper.insn)
343                         return newOper
344                 else:
345                         # This is an FC. Accesses to local symbols
346                         # are resolved at runtime.
347                         # Just set the value to the interface index.
348                         #TODO array
349                         index = block.interface.getFieldIndex(oper.value.varName)
350                         oper.interfaceIndex = index
351                 return oper
352
353         # Translate named fully qualified accesses (DBx.VARx)
354         def __resolveNamedFullyQualified(self, block, insn, oper):
355                 if oper.type != AwlOperator.NAMED_DBVAR:
356                         return oper
357
358                 # Resolve the symbolic DB name, if needed
359                 assert(oper.value.dbNumber is not None or\
360                        oper.value.dbName is not None)
361                 if oper.value.dbNumber is None:
362                         symbol = self.symbolTable.findOneByName(oper.value.dbName)
363                         if not symbol:
364                                 raise AwlSimError("Symbol \"%s\" specified as DB in "
365                                         "fully qualified operator not found." %\
366                                         oper.value.dbName)
367                         if symbol.type.type != AwlDataType.TYPE_DB_X:
368                                 raise AwlSimError("Symbol \"%s\" specified as DB in "
369                                         "fully qualified operator is not a DB-symbol." %\
370                                         oper.value.dbName)
371                         oper.value.dbNumber = symbol.operator.value.byteOffset
372
373                 # Get the DB
374                 try:
375                         db = self.dbs[oper.value.dbNumber]
376                 except KeyError as e:
377                         raise AwlSimError("DB %d specified in fully qualified "
378                                 "operator does not exist." % oper.value.dbNumber)
379
380                 # Get the data structure field descriptor
381                 # and construct the AwlOffset.
382                 field = db.struct.getField(oper.value.varName)
383                 if oper.value.indices is None:
384                         # Non-array access.
385                         if field.dataType.type == AwlDataType.TYPE_ARRAY:
386                                 #TODO this happens for parameter passing
387                                 raise AwlSimError("Variable '%s' in fully qualified "
388                                         "DB access is an ARRAY." %\
389                                         oper.value.varName)
390                 else:
391                         # This is an array access.
392                         if field.dataType.type != AwlDataType.TYPE_ARRAY:
393                                 raise AwlSimError("Indexed variable '%s' in fully qualified "
394                                         "DB access is not an ARRAY." %\
395                                         oper.value.varName)
396                         index = field.dataType.arrayIndicesCollapse(*oper.value.indices)
397                         # Get the actual data field
398                         field = db.struct.getField(oper.value.varName, index)
399                 # Extract the offset data
400                 offset = field.offset.dup()
401                 width = field.bitSize
402                 offset.dbNumber = oper.value.dbNumber
403
404                 # Construct an absolute operator
405                 return AwlOperator(type = AwlOperator.MEM_DB,
406                                    width = width,
407                                    value = offset,
408                                    insn = oper.insn)
409
410         def __resolveSymbols_block(self, block):
411                 for insn in block.insns:
412                         try:
413                                 for i, oper in enumerate(insn.ops):
414                                         oper = self.__resolveClassicSym(block, insn, oper)
415                                         oper = self.resolveNamedLocal(block, insn, oper, False)
416                                         oper = self.__resolveNamedFullyQualified(block, insn, oper)
417                                         if oper.type == AwlOperator.INDIRECT:
418                                                 oper.offsetOper = self.__resolveClassicSym(block,
419                                                                         insn, oper.offsetOper)
420                                                 oper.offsetOper = self.resolveNamedLocal(block,
421                                                                         insn, oper.offsetOper, False)
422                                         oper = self.resolveNamedLocal(block, insn, oper, True)
423                                         insn.ops[i] = oper
424                                 for param in insn.params:
425                                         oper = param.rvalueOp
426                                         oper = self.__resolveClassicSym(block, insn, oper)
427                                         oper = self.resolveNamedLocal(block, insn, oper, False)
428                                         oper = self.__resolveNamedFullyQualified(block, insn, oper)
429                                         if oper.type == AwlOperator.INDIRECT:
430                                                 oper.offsetOper = self.__resolveClassicSym(block,
431                                                                         insn, oper.offsetOper)
432                                                 oper.offsetOper = self.resolveNamedLocal(block,
433                                                                         insn, oper.offsetOper, False)
434                                         oper = self.resolveNamedLocal(block, insn, oper, False)
435                                         param.rvalueOp = oper
436                         except AwlSimError as e:
437                                 if not e.getInsn():
438                                         e.setInsn(insn)
439                                 raise e
440
441         # Resolve all symbols (global and local) on all blocks, as far as possible.
442         def __resolveSymbols(self):
443                 for ob in self.obs.values():
444                         self.__resolveSymbols_block(ob)
445                 for fb in self.fbs.values():
446                         self.__resolveSymbols_block(fb)
447                 for fc in self.fcs.values():
448                         self.__resolveSymbols_block(fc)
449                 for sfc in self.sfcs.values():
450                         self.__resolveSymbols_block(sfc)
451                         sfc.resolveHardwiredSymbols()
452                 for sfb in self.sfbs.values():
453                         self.__resolveSymbols_block(sfb)
454                         sfb.resolveHardwiredSymbols()
455
456         # Run static error checks for code block
457         def __staticSanityChecks_block(self, block):
458                 for insn in block.insns:
459                         insn.staticSanityChecks()
460
461         # Run static error checks
462         def __staticSanityChecks(self):
463                 try:
464                         self.obs[1]
465                 except KeyError:
466                         raise AwlSimError("No OB1 defined")
467
468                 for ob in self.obs.values():
469                         self.__staticSanityChecks_block(ob)
470                 for fb in self.fbs.values():
471                         self.__staticSanityChecks_block(fb)
472                 for fc in self.fcs.values():
473                         self.__staticSanityChecks_block(fc)
474
475         def load(self, parseTree):
476                 # Mnemonics autodetection
477                 self.__detectMnemonics(parseTree)
478                 # Translate OBs
479                 for obNumber, rawOB in parseTree.obs.items():
480                         obNumber = self.__resolveBlockName(AwlDataType.TYPE_OB_X,
481                                                            obNumber)
482                         if obNumber in self.obs:
483                                 raise AwlSimError("Multiple definitions of "\
484                                         "OB %d" % obNumber)
485                         rawOB.index = obNumber
486                         ob = self.__translateCodeBlock(rawOB, OB)
487                         self.obs[obNumber] = ob
488                         # Create the TEMP-preset handler table
489                         try:
490                                 presetHandlerClass = OBTempPresets_table[obNumber]
491                         except KeyError:
492                                 presetHandlerClass = OBTempPresets_dummy
493                         self.obTempPresetHandlers[obNumber] = presetHandlerClass(self)
494                 # Translate FBs
495                 for fbNumber, rawFB in parseTree.fbs.items():
496                         fbNumber = self.__resolveBlockName(AwlDataType.TYPE_FB_X,
497                                                            fbNumber)
498                         if fbNumber in self.fbs:
499                                 raise AwlSimError("Multiple definitions of "\
500                                         "FB %d" % fbNumber)
501                         rawFB.index = fbNumber
502                         fb = self.__translateCodeBlock(rawFB, FB)
503                         self.fbs[fbNumber] = fb
504                 # Translate FCs
505                 for fcNumber, rawFC in parseTree.fcs.items():
506                         fcNumber = self.__resolveBlockName(AwlDataType.TYPE_FC_X,
507                                                            fcNumber)
508                         if fcNumber in self.fcs:
509                                 raise AwlSimError("Multiple definitions of "\
510                                         "FC %d" % fcNumber)
511                         rawFC.index = fcNumber
512                         fc = self.__translateCodeBlock(rawFC, FC)
513                         self.fcs[fcNumber] = fc
514
515                 if not self.sfbs:
516                         # Create the SFB tables
517                         for sfbNumber in SFB_table.keys():
518                                 if sfbNumber < 0 and not self.__extendedInsnsEnabled:
519                                         continue
520                                 sfb = SFB_table[sfbNumber](self)
521                                 sfb.interface.buildDataStructure()
522                                 self.sfbs[sfbNumber] = sfb
523                 if not self.sfcs:
524                         # Create the SFC tables
525                         for sfcNumber in SFC_table.keys():
526                                 if sfcNumber < 0 and not self.__extendedInsnsEnabled:
527                                         continue
528                                 sfc = SFC_table[sfcNumber](self)
529                                 sfc.interface.buildDataStructure()
530                                 self.sfcs[sfcNumber] = sfc
531
532                 # Translate DBs
533                 for dbNumber, rawDB in parseTree.dbs.items():
534                         dbNumber = self.__resolveBlockName(AwlDataType.TYPE_DB_X,
535                                                            dbNumber)
536                         if dbNumber in self.dbs:
537                                 raise AwlSimError("Multiple definitions of "\
538                                         "DB %d" % dbNumber)
539                         rawDB.index = dbNumber
540                         db = self.__translateDB(rawDB)
541                         self.dbs[dbNumber] = db
542
543         def loadSymbolTable(self, symbolTable):
544                 self.symbolTable.merge(symbolTable)
545
546         def reallocate(self, force=False):
547                 if force or (self.specs.nrAccus == 4) != self.is4accu:
548                         self.accu1, self.accu2, self.accu3, self.accu4 =\
549                                 Accu(), Accu(), Accu(), Accu()
550                         self.is4accu = (self.specs.nrAccus == 4)
551                 if force or self.specs.nrTimers != len(self.timers):
552                         self.timers = [ Timer(self, i)
553                                         for i in range(self.specs.nrTimers) ]
554                 if force or self.specs.nrCounters != len(self.counters):
555                         self.counters = [ Counter(self, i)
556                                           for i in range(self.specs.nrCounters) ]
557                 if force or self.specs.nrFlags != len(self.flags):
558                         self.flags = ByteArray(self.specs.nrFlags)
559                 if force or self.specs.nrInputs != len(self.inputs):
560                         self.inputs = ByteArray(self.specs.nrInputs)
561                 if force or self.specs.nrOutputs != len(self.outputs):
562                         self.outputs = ByteArray(self.specs.nrOutputs)
563                 CallStackElem.resetCache()
564
565         def reset(self):
566                 self.dbs = {
567                         # DBs
568                         0 : DB(0, permissions = 0), # read/write-protected system-DB
569                 }
570                 self.obs = {
571                         # OBs
572                 }
573                 self.obTempPresetHandlers = {
574                         # OB TEMP-preset handlers
575                 }
576                 self.fcs = {
577                         # User FCs
578                 }
579                 self.fbs = {
580                         # User FBs
581                 }
582                 self.sfcs = {
583                         # System SFCs
584                 }
585                 self.sfbs = {
586                         # System SFBs
587                 }
588                 self.symbolTable = SymbolTable()
589                 self.is4accu = False
590                 self.reallocate(force=True)
591                 self.ar1 = Adressregister()
592                 self.ar2 = Adressregister()
593                 self.dbRegister = self.dbs[0]
594                 self.diRegister = self.dbs[0]
595                 self.callStack = [ ]
596                 self.callStackTop = None
597                 self.setMcrActive(False)
598                 self.mcrStack = [ ]
599                 self.statusWord = S7StatusWord()
600
601                 self.relativeJump = 1
602
603                 # Stats
604                 self.__insnCount = 0
605                 self.__cycleCount = 0
606                 self.insnPerSecond = 0.0
607                 self.avgInsnPerCycle = 0.0
608                 self.cycleStartTime = 0.0
609                 self.minCycleTime = 86400.0
610                 self.maxCycleTime = 0.0
611                 self.avgCycleTime = 0.0
612                 self.startupTime = 0.0
613                 self.__speedMeasureStartTime = 0
614                 self.__speedMeasureStartInsnCount = 0
615                 self.__speedMeasureStartCycleCount = 0
616
617                 self.updateTimestamp()
618
619         def setCycleExitCallback(self, cb, data=None):
620                 self.cbCycleExit = cb
621                 self.cbCycleExitData = data
622
623         def setBlockExitCallback(self, cb, data=None):
624                 self.cbBlockExit = cb
625                 self.cbBlockExitData = data
626
627         def setPostInsnCallback(self, cb, data=None):
628                 self.cbPostInsn = cb
629                 self.cbPostInsnData = data
630
631         def setPeripheralReadCallback(self, cb, data=None):
632                 self.cbPeripheralRead = cb
633                 self.cbPeripheralReadData = data
634
635         def setPeripheralWriteCallback(self, cb, data=None):
636                 self.cbPeripheralWrite = cb
637                 self.cbPeripheralWriteData = data
638
639         def setScreenUpdateCallback(self, cb, data=None):
640                 self.cbScreenUpdate = cb
641                 self.cbScreenUpdateData = data
642
643         def requestScreenUpdate(self):
644                 if self.cbScreenUpdate:
645                         self.cbScreenUpdate(self.cbScreenUpdateData)
646
647         def __runOB(self, block):
648                 # Update timekeeping
649                 self.updateTimestamp()
650                 self.cycleStartTime = self.now
651
652                 # Initialize CPU state
653                 self.callStack = [ CallStackElem(self, block) ]
654                 self.dbRegister = self.diRegister = self.dbs[0]
655                 cse = self.callStackTop = self.callStack[-1]
656                 if self.__obTempPresetsEnabled:
657                         # Populate the TEMP region
658                         self.obTempPresetHandlers[block.index].generate(cse.localdata)
659
660                 # Run the user program cycle
661                 while self.callStack:
662                         while cse.ip < len(cse.insns):
663                                 insn, self.relativeJump = cse.insns[cse.ip], 1
664                                 insn.run()
665                                 if self.cbPostInsn:
666                                         self.cbPostInsn(self.cbPostInsnData)
667                                 cse.ip += self.relativeJump
668                                 cse, self.__insnCount = self.callStackTop,\
669                                                         (self.__insnCount + 1) & 0x3FFFFFFF
670                                 if not self.__insnCount % 64:
671                                         self.updateTimestamp()
672                                         self.__runTimeCheck()
673                         if self.cbBlockExit:
674                                 self.cbBlockExit(self.cbBlockExitData)
675                         prevCse = self.callStack.pop()
676                         if self.callStack:
677                                 cse = self.callStackTop = self.callStack[-1]
678                                 prevCse.handleBlockExit()
679                         prevCse.destroy()
680                 if self.cbCycleExit:
681                         self.cbCycleExit(self.cbCycleExitData)
682
683         # Run startup code
684         def startup(self):
685                 # Resolve symbolic instructions and operators
686                 self.__resolveSymbols()
687                 # Run some static sanity checks on the code
688                 self.__staticSanityChecks()
689
690                 self.updateTimestamp()
691                 self.__speedMeasureStartTime = self.now
692                 self.__speedMeasureStartInsnCount = 0
693                 self.__speedMeasureStartCycleCount = 0
694                 self.startupTime = self.now
695
696                 # Run startup OB
697                 if 102 in self.obs and self.is4accu:
698                         # Cold start.
699                         # This is only done on 4xx-series CPUs.
700                         self.__runOB(self.obs[102])
701                 elif 100 in self.obs:
702                         # Warm start.
703                         # This really is a cold start, because remanent
704                         # resources were reset. However we could not execute
705                         # OB 102, so this is a fallback.
706                         # This is not 100% compliant with real CPUs, but it probably
707                         # is sane behavior.
708                         self.__runOB(self.obs[100])
709
710         # Run one cycle of the user program
711         def runCycle(self):
712                 # Run the actual OB1 code
713                 self.__runOB(self.obs[1])
714
715                 # Update timekeeping and statistics
716                 self.updateTimestamp()
717                 self.__cycleCount = (self.__cycleCount + 1) & 0x3FFFFFFF
718
719                 # Evaluate speed measurement
720                 elapsedTime = self.now - self.__speedMeasureStartTime
721                 if elapsedTime >= 1.0:
722                         # Calculate instruction and cycle counts.
723                         cycleCount = (self.__cycleCount - self.__speedMeasureStartCycleCount) &\
724                                      0x3FFFFFFF
725                         insnCount = (self.__insnCount - self.__speedMeasureStartInsnCount) &\
726                                     0x3FFFFFFF
727
728                         # Calculate instruction statistics.
729                         self.insnPerSecond = insnCount / elapsedTime
730                         self.avgInsnPerCycle = insnCount / cycleCount
731
732                         # Get the average cycle time over the measurement period.
733                         cycleTime = elapsedTime / cycleCount
734
735                         # Store overall-average cycle time and maximum cycle time.
736                         self.maxCycleTime = max(self.maxCycleTime, cycleTime)
737                         self.minCycleTime = min(self.minCycleTime, cycleTime)
738                         self.avgCycleTime = (self.avgCycleTime + cycleTime) / 2.0
739
740                         # Reset the counters
741                         self.__speedMeasureStartTime = self.now
742                         self.__speedMeasureStartInsnCount = self.__insnCount
743                         self.__speedMeasureStartCycleCount = self.__cycleCount
744
745         __getTime = getattr(time, "perf_counter", time.time)
746
747         # updateTimestamp() updates self.now, which is a
748         # floating point count of seconds.
749         def updateTimestamp(self):
750                 self.now = self.__getTime()
751
752         __dateAndTimeWeekdayMap = {
753                 0       : 2,    # monday
754                 1       : 3,    # tuesday
755                 2       : 4,    # wednesday
756                 3       : 5,    # thursday
757                 4       : 6,    # friday
758                 5       : 7,    # saturday
759                 6       : 1,    # sunday
760         }
761
762         # Make a DATE_AND_TIME for the current wall-time and
763         # store it in byteArray, which is a list of GenericByte objects.
764         # If byteArray is smaller than 8 bytes, an IndexError is raised.
765         def makeCurrentDateAndTime(self, byteArray, offset):
766                 dt = datetime.datetime.now()
767                 year, month, day, hour, minute, second, msec =\
768                         dt.year, dt.month, dt.day, dt.hour, \
769                         dt.minute, dt.second, dt.microsecond // 1000
770                 byteArray[offset] = (year % 10) | (((year // 10) % 10) << 4)
771                 byteArray[offset + 1] = (month % 10) | (((month // 10) % 10) << 4)
772                 byteArray[offset + 2] = (day % 10) | (((day // 10) % 10) << 4)
773                 byteArray[offset + 3] = (hour % 10) | (((hour // 10) % 10) << 4)
774                 byteArray[offset + 4] = (minute % 10) | (((minute // 10) % 10) << 4)
775                 byteArray[offset + 5] = (second % 10) | (((second // 10) % 10) << 4)
776                 byteArray[offset + 6] = ((msec // 10) % 10) | (((msec // 100) % 10) << 4)
777                 byteArray[offset + 7] = ((msec % 10) << 4) |\
778                                         self.__dateAndTimeWeekdayMap[dt.weekday()]
779
780         def __runTimeCheck(self):
781                 if self.now - self.cycleStartTime > self.cycleTimeLimit:
782                         raise AwlSimError("Cycle time exceed %.3f seconds" %\
783                                           self.cycleTimeLimit)
784
785         def getCurrentIP(self):
786                 try:
787                         return self.callStackTop.ip
788                 except IndexError as e:
789                         return None
790
791         def getCurrentInsn(self):
792                 try:
793                         cse = self.callStackTop
794                         if not cse:
795                                 return None
796                         return cse.insns[cse.ip]
797                 except IndexError as e:
798                         return None
799
800         def labelIdxToRelJump(self, labelIndex):
801                 # Translate a label index into a relative IP offset.
802                 cse = self.callStackTop
803                 label = cse.block.labels[labelIndex]
804                 return label.getInsn().getIP() - cse.ip
805
806         def jumpToLabel(self, labelIndex):
807                 self.relativeJump = self.labelIdxToRelJump(labelIndex)
808
809         def jumpRelative(self, insnOffset):
810                 self.relativeJump = insnOffset
811
812         def __call_FC(self, blockOper, dbOper, parameters):
813                 fc = self.fcs[blockOper.value.byteOffset]
814                 return CallStackElem(self, fc, None, parameters)
815
816         def __call_RAW_FC(self, blockOper, dbOper, parameters):
817                 fc = self.fcs[blockOper.value.byteOffset]
818                 return CallStackElem(self, fc, None, (), True)
819
820         def __call_FB(self, blockOper, dbOper, parameters):
821                 fb = self.fbs[blockOper.value.byteOffset]
822                 db = self.dbs[dbOper.value.byteOffset]
823                 cse = CallStackElem(self, fb, db, parameters)
824                 self.dbRegister, self.diRegister = self.diRegister, db
825                 return cse
826
827         def __call_RAW_FB(self, blockOper, dbOper, parameters):
828                 fb = self.fbs[blockOper.value.byteOffset]
829                 return CallStackElem(self, fb, self.diRegister, (), True)
830
831         def __call_SFC(self, blockOper, dbOper, parameters):
832                 sfc = self.sfcs[blockOper.value.byteOffset]
833                 return CallStackElem(self, sfc, None, parameters)
834
835         def __call_RAW_SFC(self, blockOper, dbOper, parameters):
836                 sfc = self.sfcs[blockOper.value.byteOffset]
837                 return CallStackElem(self, sfc, None, (), True)
838
839         def __call_SFB(self, blockOper, dbOper, parameters):
840                 sfb = self.sfbs[blockOper.value.byteOffset]
841                 db = self.dbs[dbOper.value.byteOffset]
842                 cse = CallStackElem(self, sfb, db, parameters)
843                 self.dbRegister, self.diRegister = self.diRegister, db
844                 return cse
845
846         def __call_RAW_SFB(self, blockOper, dbOper, parameters):
847                 sfb = self.sfbs[blockOper.value.byteOffset]
848                 return CallStackElem(self, sfb, self.diRegister, (), True)
849
850         def __call_INDIRECT(self, blockOper, dbOper, parameters):
851                 blockOper = blockOper.resolve()
852                 callHelper = self.__rawCallHelpers[blockOper.type]
853                 try:
854                         return callHelper(self, blockOper, dbOper, parameters)
855                 except KeyError as e:
856                         raise AwlSimError("Code block %d not found in indirect call" %\
857                                           blockOper.value.byteOffset)
858
859         __callHelpers = {
860                 AwlOperator.BLKREF_FC   : __call_FC,
861                 AwlOperator.BLKREF_FB   : __call_FB,
862                 AwlOperator.BLKREF_SFC  : __call_SFC,
863                 AwlOperator.BLKREF_SFB  : __call_SFB,
864         }
865
866         __rawCallHelpers = {
867                 AwlOperator.BLKREF_FC   : __call_RAW_FC,
868                 AwlOperator.BLKREF_FB   : __call_RAW_FB,
869                 AwlOperator.BLKREF_SFC  : __call_RAW_SFC,
870                 AwlOperator.BLKREF_SFB  : __call_RAW_SFB,
871                 AwlOperator.INDIRECT    : __call_INDIRECT,
872         }
873
874         def run_CALL(self, blockOper, dbOper=None, parameters=(), raw=False):
875                 try:
876                         if raw:
877                                 callHelper = self.__rawCallHelpers[blockOper.type]
878                         else:
879                                 callHelper = self.__callHelpers[blockOper.type]
880                 except KeyError:
881                         raise AwlSimError("Invalid CALL operand")
882                 newCse = callHelper(self, blockOper, dbOper, parameters)
883                 if newCse:
884                         self.callStack.append(newCse)
885                         self.callStackTop = newCse
886
887         def run_BE(self):
888                 s = self.statusWord
889                 s.OS, s.OR, s.STA, s.NER = 0, 0, 1, 0
890                 # Jump beyond end of block
891                 cse = self.callStackTop
892                 self.relativeJump = len(cse.insns) - cse.ip
893
894         def run_AUF(self, dbOper):
895                 dbOper = dbOper.resolve()
896                 try:
897                         db = self.dbs[dbOper.value.byteOffset]
898                 except KeyError:
899                         raise AwlSimError("Datablock %i does not exist" %\
900                                           dbOper.value.byteOffset)
901                 if dbOper.type == AwlOperator.BLKREF_DB:
902                         self.dbRegister = db
903                 elif dbOper.type == AwlOperator.BLKREF_DI:
904                         self.diRegister = db
905                 else:
906                         raise AwlSimError("Invalid DB reference in AUF")
907
908         def run_TDB(self):
909                 # Swap global and instance DB
910                 self.diRegister, self.dbRegister = self.dbRegister, self.diRegister
911
912         def getStatusWord(self):
913                 return self.statusWord
914
915         def getAccu(self, index):
916                 if index < 1 or index > self.specs.nrAccus:
917                         raise AwlSimError("Invalid ACCU offset")
918                 return (self.accu1, self.accu2,
919                         self.accu3, self.accu4)[index - 1]
920
921         def getAR(self, index):
922                 if index < 1 or index > 2:
923                         raise AwlSimError("Invalid AR offset")
924                 return (self.ar1, self.ar2)[index - 1]
925
926         def getTimer(self, index):
927                 try:
928                         return self.timers[index]
929                 except IndexError as e:
930                         raise AwlSimError("Fetched invalid timer %d" % index)
931
932         def getCounter(self, index):
933                 try:
934                         return self.counters[index]
935                 except IndexError as e:
936                         raise AwlSimError("Fetched invalid counter %d" % index)
937
938         def getSpecs(self):
939                 return self.specs
940
941         def setMcrActive(self, active):
942                 self.mcrActive = active
943
944         def mcrIsOn(self):
945                 return (not self.mcrActive or all(self.mcrStack))
946
947         def mcrStackAppend(self, statusWord):
948                 self.mcrStack.append(McrStackElem(statusWord))
949                 if len(self.mcrStack) > 8:
950                         raise AwlSimError("MCR stack overflow")
951
952         def mcrStackPop(self):
953                 try:
954                         return self.mcrStack.pop()
955                 except IndexError:
956                         raise AwlSimError("MCR stack underflow")
957
958         def parenStackAppend(self, insnType, statusWord):
959                 cse = self.callStackTop
960                 cse.parenStack.append(ParenStackElem(self, insnType, statusWord))
961                 if len(cse.parenStack) > 7:
962                         raise AwlSimError("Parenthesis stack overflow")
963
964         # Fetch a range in the 'output' memory area.
965         # 'byteOffset' is the byte offset into the output area.
966         # 'byteCount' is the number if bytes to fetch.
967         # Returns a bytearray.
968         def fetchOutputRange(self, byteOffset, byteCount):
969                 return self.outputs[byteOffset : byteOffset + byteCount]
970
971         # Store a range in the 'input' memory area.
972         # 'byteOffset' is the byte offset into the input area.
973         # 'data' is a bytearray.
974         def storeInputRange(self, byteOffset, data):
975                 self.inputs[byteOffset : byteOffset + len(data)] = data
976
977         def fetch(self, operator, enforceWidth=()):
978                 try:
979                         fetchMethod = self.fetchTypeMethods[operator.type]
980                 except KeyError:
981                         raise AwlSimError("Invalid fetch request: %s" %\
982                                 AwlOperator.type2str[operator.type])
983                 return fetchMethod(self, operator, enforceWidth)
984
985         def __fetchWidthError(self, operator, enforceWidth):
986                 raise AwlSimError("Data fetch of %d bits, "
987                         "but only %s bits are allowed." %\
988                         (operator.width,
989                          listToHumanStr(enforceWidth)))
990
991         def fetchIMM(self, operator, enforceWidth):
992                 if operator.width not in enforceWidth and enforceWidth:
993                         self.__fetchWidthError(operator, enforceWidth)
994
995                 return operator.value
996
997         def fetchDBLG(self, operator, enforceWidth):
998                 if operator.width not in enforceWidth and enforceWidth:
999                         self.__fetchWidthError(operator, enforceWidth)
1000
1001                 return self.dbRegister.struct.getSize()
1002
1003         def fetchDBNO(self, operator, enforceWidth):
1004                 if operator.width not in enforceWidth and enforceWidth:
1005                         self.__fetchWidthError(operator, enforceWidth)
1006
1007                 return self.dbRegister.index
1008
1009         def fetchDILG(self, operator, enforceWidth):
1010                 if operator.width not in enforceWidth and enforceWidth:
1011                         self.__fetchWidthError(operator, enforceWidth)
1012
1013                 return self.diRegister.struct.getSize()
1014
1015         def fetchDINO(self, operator, enforceWidth):
1016                 if operator.width not in enforceWidth and enforceWidth:
1017                         self.__fetchWidthError(operator, enforceWidth)
1018
1019                 return self.diRegister.index
1020
1021         def fetchAR2(self, operator, enforceWidth):
1022                 if operator.width not in enforceWidth and enforceWidth:
1023                         self.__fetchWidthError(operator, enforceWidth)
1024
1025                 return self.getAR(2).get()
1026
1027         def fetchSTW(self, operator, enforceWidth):
1028                 if operator.width not in enforceWidth and enforceWidth:
1029                         self.__fetchWidthError(operator, enforceWidth)
1030
1031                 if operator.width == 1:
1032                         return self.statusWord.getByBitNumber(operator.value.bitOffset)
1033                 elif operator.width == 16:
1034                         return self.statusWord.getWord()
1035                 else:
1036                         assert(0)
1037
1038         def fetchSTW_Z(self, operator, enforceWidth):
1039                 if operator.width not in enforceWidth and enforceWidth:
1040                         self.__fetchWidthError(operator, enforceWidth)
1041
1042                 return (self.statusWord.A0 ^ 1) & (self.statusWord.A1 ^ 1)
1043
1044         def fetchSTW_NZ(self, operator, enforceWidth):
1045                 if operator.width not in enforceWidth and enforceWidth:
1046                         self.__fetchWidthError(operator, enforceWidth)
1047
1048                 return self.statusWord.A0 | self.statusWord.A1
1049
1050         def fetchSTW_POS(self, operator, enforceWidth):
1051                 if operator.width not in enforceWidth and enforceWidth:
1052                         self.__fetchWidthError(operator, enforceWidth)
1053
1054                 return (self.statusWord.A0 ^ 1) & self.statusWord.A1
1055
1056         def fetchSTW_NEG(self, operator, enforceWidth):
1057                 if operator.width not in enforceWidth and enforceWidth:
1058                         self.__fetchWidthError(operator, enforceWidth)
1059
1060                 return self.statusWord.A0 & (self.statusWord.A1 ^ 1)
1061
1062         def fetchSTW_POSZ(self, operator, enforceWidth):
1063                 if operator.width not in enforceWidth and enforceWidth:
1064                         self.__fetchWidthError(operator, enforceWidth)
1065
1066                 return self.statusWord.A0 ^ 1
1067
1068         def fetchSTW_NEGZ(self, operator, enforceWidth):
1069                 if operator.width not in enforceWidth and enforceWidth:
1070                         self.__fetchWidthError(operator, enforceWidth)
1071
1072                 return self.statusWord.A1 ^ 1
1073
1074         def fetchSTW_UO(self, operator, enforceWidth):
1075                 if operator.width not in enforceWidth and enforceWidth:
1076                         self.__fetchWidthError(operator, enforceWidth)
1077
1078                 return self.statusWord.A0 & self.statusWord.A1
1079
1080         def fetchE(self, operator, enforceWidth):
1081                 if operator.width not in enforceWidth and enforceWidth:
1082                         self.__fetchWidthError(operator, enforceWidth)
1083
1084                 return self.inputs.fetch(operator.value, operator.width)
1085
1086         def fetchA(self, operator, enforceWidth):
1087                 if operator.width not in enforceWidth and enforceWidth:
1088                         self.__fetchWidthError(operator, enforceWidth)
1089
1090                 return self.outputs.fetch(operator.value, operator.width)
1091
1092         def fetchM(self, operator, enforceWidth):
1093                 if operator.width not in enforceWidth and enforceWidth:
1094                         self.__fetchWidthError(operator, enforceWidth)
1095
1096                 return self.flags.fetch(operator.value, operator.width)
1097
1098         def fetchL(self, operator, enforceWidth):
1099                 if operator.width not in enforceWidth and enforceWidth:
1100                         self.__fetchWidthError(operator, enforceWidth)
1101
1102                 return self.callStackTop.localdata.fetch(operator.value, operator.width)
1103
1104         def fetchVL(self, operator, enforceWidth):
1105                 if operator.width not in enforceWidth and enforceWidth:
1106                         self.__fetchWidthError(operator, enforceWidth)
1107
1108                 try:
1109                         cse = self.callStack[-2]
1110                 except IndexError:
1111                         raise AwlSimError("Fetch of parent localstack, "
1112                                 "but no parent present.")
1113                 return cse.localdata.fetch(operator.value, operator.width)
1114
1115         def fetchDB(self, operator, enforceWidth):
1116                 if operator.width not in enforceWidth and enforceWidth:
1117                         self.__fetchWidthError(operator, enforceWidth)
1118
1119                 if operator.value.dbNumber is not None:
1120                         # This is a fully qualified access (DBx.DBx X)
1121                         # Open the data block first.
1122                         self.run_AUF(AwlOperator(AwlOperator.BLKREF_DB, 16,
1123                                                  AwlOffset(operator.value.dbNumber),
1124                                                  operator.insn))
1125                 if not self.dbRegister:
1126                         raise AwlSimError("Fetch from global DB, "
1127                                 "but no DB is opened")
1128                 return self.dbRegister.fetch(operator)
1129
1130         def fetchDI(self, operator, enforceWidth):
1131                 if operator.width not in enforceWidth and enforceWidth:
1132                         self.__fetchWidthError(operator, enforceWidth)
1133
1134                 if not self.diRegister:
1135                         raise AwlSimError("Fetch from instance DI, "
1136                                 "but no DI is opened")
1137                 return self.diRegister.fetch(operator)
1138
1139         def fetchPE(self, operator, enforceWidth):
1140                 if operator.width not in enforceWidth and enforceWidth:
1141                         self.__fetchWidthError(operator, enforceWidth)
1142
1143                 value = None
1144                 if self.cbPeripheralRead:
1145                         value = self.cbPeripheralRead(self.cbPeripheralReadData,
1146                                                       operator.width,
1147                                                       operator.value.byteOffset)
1148                 if value is None:
1149                         raise AwlSimError("There is no hardware to handle "
1150                                 "the direct peripheral fetch. "
1151                                 "(width=%d, offset=%d)" %\
1152                                 (operator.width, operator.value.byteOffset))
1153                 self.inputs.store(operator.value, operator.width, value)
1154                 return self.inputs.fetch(operator.value, operator.width)
1155
1156         def fetchT(self, operator, enforceWidth):
1157                 insnType = operator.insn.type
1158                 if insnType == AwlInsn.TYPE_L or insnType == AwlInsn.TYPE_LC:
1159                         width = 32
1160                 else:
1161                         width = 1
1162                 if width not in enforceWidth and enforceWidth:
1163                         self.__fetchWidthError(operator, enforceWidth)
1164
1165                 timer = self.getTimer(operator.value.byteOffset)
1166                 if insnType == AwlInsn.TYPE_L:
1167                         return timer.getTimevalBin()
1168                 elif insnType == AwlInsn.TYPE_LC:
1169                         return timer.getTimevalS5T()
1170                 return timer.get()
1171
1172         def fetchZ(self, operator, enforceWidth):
1173                 insnType = operator.insn.type
1174                 if insnType == AwlInsn.TYPE_L or insnType == AwlInsn.TYPE_LC:
1175                         width = 32
1176                 else:
1177                         width = 1
1178                 if width not in enforceWidth and enforceWidth:
1179                         self.__fetchWidthError(operator, enforceWidth)
1180
1181                 counter = self.getCounter(operator.value.byteOffset)
1182                 if insnType == AwlInsn.TYPE_L:
1183                         return counter.getValueBin()
1184                 elif insnType == AwlInsn.TYPE_LC:
1185                         return counter.getValueBCD()
1186                 return counter.get()
1187
1188         def fetchNAMED_LOCAL(self, operator, enforceWidth):
1189                 # load from an FC interface field.
1190                 return self.fetch(self.callStackTop.interfRefs[operator.interfaceIndex].resolve(False),
1191                                   enforceWidth)
1192
1193         def fetchNAMED_LOCAL_PTR(self, operator, enforceWidth):
1194                 return self.callStackTop.interfRefs[operator.interfaceIndex].resolve(False).makePointer()
1195
1196         def fetchNAMED_DBVAR(self, operator, enforceWidth):
1197                 # All legit accesses will have been translated to absolute addressing already
1198                 raise AwlSimError("Fully qualified load from DB variable "
1199                         "is not supported in this place.")
1200
1201         def fetchINDIRECT(self, operator, enforceWidth):
1202                 return self.fetch(operator.resolve(False), enforceWidth)
1203
1204         def fetchVirtACCU(self, operator, enforceWidth):
1205                 if operator.width not in enforceWidth and enforceWidth:
1206                         self.__fetchWidthError(operator, enforceWidth)
1207
1208                 return self.getAccu(operator.value.byteOffset).get()
1209
1210         def fetchVirtAR(self, operator, enforceWidth):
1211                 if operator.width not in enforceWidth and enforceWidth:
1212                         self.__fetchWidthError(operator, enforceWidth)
1213
1214                 return self.getAR(operator.value.byteOffset).get()
1215
1216         def fetchVirtDBR(self, operator, enforceWidth):
1217                 if operator.width not in enforceWidth and enforceWidth:
1218                         self.__fetchWidthError(operator, enforceWidth)
1219
1220                 if operator.value.byteOffset == 1:
1221                         if self.dbRegister:
1222                                 return self.dbRegister.index
1223                 elif operator.value.byteOffset == 2:
1224                         if self.diRegister:
1225                                 return self.diRegister.index
1226                 else:
1227                         raise AwlSimError("Invalid __DBR %d. "
1228                                 "Must be 1 for DB-register or "
1229                                 "2 for DI-register." %\
1230                                 operator.value.byteOffset)
1231                 return 0
1232
1233         fetchTypeMethods = {
1234                 AwlOperator.IMM                 : fetchIMM,
1235                 AwlOperator.IMM_REAL            : fetchIMM,
1236                 AwlOperator.IMM_S5T             : fetchIMM,
1237                 AwlOperator.IMM_TIME            : fetchIMM,
1238                 AwlOperator.IMM_DATE            : fetchIMM,
1239                 AwlOperator.IMM_TOD             : fetchIMM,
1240                 AwlOperator.IMM_PTR             : fetchIMM,
1241                 AwlOperator.MEM_E               : fetchE,
1242                 AwlOperator.MEM_A               : fetchA,
1243                 AwlOperator.MEM_M               : fetchM,
1244                 AwlOperator.MEM_L               : fetchL,
1245                 AwlOperator.MEM_VL              : fetchVL,
1246                 AwlOperator.MEM_DB              : fetchDB,
1247                 AwlOperator.MEM_DI              : fetchDI,
1248                 AwlOperator.MEM_T               : fetchT,
1249                 AwlOperator.MEM_Z               : fetchZ,
1250                 AwlOperator.MEM_PE              : fetchPE,
1251                 AwlOperator.MEM_DBLG            : fetchDBLG,
1252                 AwlOperator.MEM_DBNO            : fetchDBNO,
1253                 AwlOperator.MEM_DILG            : fetchDILG,
1254                 AwlOperator.MEM_DINO            : fetchDINO,
1255                 AwlOperator.MEM_AR2             : fetchAR2,
1256                 AwlOperator.MEM_STW             : fetchSTW,
1257                 AwlOperator.MEM_STW_Z           : fetchSTW_Z,
1258                 AwlOperator.MEM_STW_NZ          : fetchSTW_NZ,
1259                 AwlOperator.MEM_STW_POS         : fetchSTW_POS,
1260                 AwlOperator.MEM_STW_NEG         : fetchSTW_NEG,
1261                 AwlOperator.MEM_STW_POSZ        : fetchSTW_POSZ,
1262                 AwlOperator.MEM_STW_NEGZ        : fetchSTW_NEGZ,
1263                 AwlOperator.MEM_STW_UO          : fetchSTW_UO,
1264                 AwlOperator.NAMED_LOCAL         : fetchNAMED_LOCAL,
1265                 AwlOperator.NAMED_LOCAL_PTR     : fetchNAMED_LOCAL_PTR,
1266                 AwlOperator.NAMED_DBVAR         : fetchNAMED_DBVAR,
1267                 AwlOperator.INDIRECT            : fetchINDIRECT,
1268                 AwlOperator.VIRT_ACCU           : fetchVirtACCU,
1269                 AwlOperator.VIRT_AR             : fetchVirtAR,
1270                 AwlOperator.VIRT_DBR            : fetchVirtDBR,
1271         }
1272
1273         def store(self, operator, value, enforceWidth=()):
1274                 try:
1275                         storeMethod = self.storeTypeMethods[operator.type]
1276                 except KeyError:
1277                         raise AwlSimError("Invalid store request")
1278                 storeMethod(self, operator, value, enforceWidth)
1279
1280         def __storeWidthError(self, operator, enforceWidth):
1281                 raise AwlSimError("Data store of %d bits, "
1282                         "but only %s bits are allowed." %\
1283                         (operator.width,
1284                          listToHumanStr(enforceWidth)))
1285
1286         def storeE(self, operator, value, enforceWidth):
1287                 if operator.width not in enforceWidth and enforceWidth:
1288                         self.__storeWidthError(operator, enforceWidth)
1289
1290                 self.inputs.store(operator.value, operator.width, value)
1291
1292         def storeA(self, operator, value, enforceWidth):
1293                 if operator.width not in enforceWidth and enforceWidth:
1294                         self.__storeWidthError(operator, enforceWidth)
1295
1296                 self.outputs.store(operator.value, operator.width, value)
1297
1298         def storeM(self, operator, value, enforceWidth):
1299                 if operator.width not in enforceWidth and enforceWidth:
1300                         self.__storeWidthError(operator, enforceWidth)
1301
1302                 self.flags.store(operator.value, operator.width, value)
1303
1304         def storeL(self, operator, value, enforceWidth):
1305                 if operator.width not in enforceWidth and enforceWidth:
1306                         self.__storeWidthError(operator, enforceWidth)
1307
1308                 self.callStackTop.localdata.store(operator.value, operator.width, value)
1309
1310         def storeVL(self, operator, value, enforceWidth):
1311                 if operator.width not in enforceWidth and enforceWidth:
1312                         self.__storeWidthError(operator, enforceWidth)
1313
1314                 try:
1315                         cse = self.callStack[-2]
1316                 except IndexError:
1317                         raise AwlSimError("Store to parent localstack, "
1318                                 "but no parent present.")
1319                 cse.localdata.store(operator.value, operator.width, value)
1320
1321         def storeDB(self, operator, value, enforceWidth):
1322                 if operator.width not in enforceWidth and enforceWidth:
1323                         self.__storeWidthError(operator, enforceWidth)
1324
1325                 if operator.value.dbNumber is None:
1326                         db = self.dbRegister
1327                         if not db:
1328                                 raise AwlSimError("Store to global DB, "
1329                                         "but no DB is opened")
1330                 else:
1331                         try:
1332                                 db = self.dbs[operator.value.dbNumber]
1333                         except KeyError:
1334                                 raise AwlSimError("Store to DB %d, but DB "
1335                                         "does not exist" % operator.value.dbNumber)
1336                 db.store(operator, value)
1337
1338         def storeDI(self, operator, value, enforceWidth):
1339                 if operator.width not in enforceWidth and enforceWidth:
1340                         self.__storeWidthError(operator, enforceWidth)
1341
1342                 if not self.diRegister:
1343                         raise AwlSimError("Store to instance DI, "
1344                                 "but no DI is opened")
1345                 self.diRegister.store(operator, value)
1346
1347         def storePA(self, operator, value, enforceWidth):
1348                 if operator.width not in enforceWidth and enforceWidth:
1349                         self.__storeWidthError(operator, enforceWidth)
1350
1351                 self.outputs.store(operator.value, operator.width, value)
1352                 ok = False
1353                 if self.cbPeripheralWrite:
1354                         ok = self.cbPeripheralWrite(self.cbPeripheralWriteData,
1355                                                     operator.width,
1356                                                     operator.value.byteOffset,
1357                                                     value)
1358                 if not ok:
1359                         raise AwlSimError("There is no hardware to handle "
1360                                 "the direct peripheral store. "
1361                                 "(width=%d, offset=%d, value=0x%X)" %\
1362                                 (operator.width, operator.value.byteOffset,
1363                                  value))
1364
1365         def storeAR2(self, operator, value, enforceWidth):
1366                 if operator.width not in enforceWidth and enforceWidth:
1367                         self.__storeWidthError(operator, enforceWidth)
1368
1369                 self.getAR(2).set(value)
1370
1371         def storeSTW(self, operator, value, enforceWidth):
1372                 if operator.width not in enforceWidth and enforceWidth:
1373                         self.__storeWidthError(operator, enforceWidth)
1374
1375                 if operator.width == 1:
1376                         raise AwlSimError("Cannot store to individual STW bits")
1377                 elif operator.width == 16:
1378                         self.statusWord.setWord(value)
1379                 else:
1380                         assert(0)
1381
1382         def storeNAMED_LOCAL(self, operator, value, enforceWidth):
1383                 # store to an FC interface field.
1384                 self.store(self.callStackTop.interfRefs[operator.interfaceIndex].resolve(True),
1385                            value, enforceWidth)
1386
1387         def storeNAMED_DBVAR(self, operator, value, enforceWidth):
1388                 # All legit accesses will have been translated to absolute addressing already
1389                 raise AwlSimError("Fully qualified store to DB variable "
1390                         "is not supported in this place.")
1391
1392         def storeINDIRECT(self, operator, value, enforceWidth):
1393                 self.store(operator.resolve(True), value, enforceWidth)
1394
1395         storeTypeMethods = {
1396                 AwlOperator.MEM_E               : storeE,
1397                 AwlOperator.MEM_A               : storeA,
1398                 AwlOperator.MEM_M               : storeM,
1399                 AwlOperator.MEM_L               : storeL,
1400                 AwlOperator.MEM_VL              : storeVL,
1401                 AwlOperator.MEM_DB              : storeDB,
1402                 AwlOperator.MEM_DI              : storeDI,
1403                 AwlOperator.MEM_PA              : storePA,
1404                 AwlOperator.MEM_AR2             : storeAR2,
1405                 AwlOperator.MEM_STW             : storeSTW,
1406                 AwlOperator.NAMED_LOCAL         : storeNAMED_LOCAL,
1407                 AwlOperator.NAMED_DBVAR         : storeNAMED_DBVAR,
1408                 AwlOperator.INDIRECT            : storeINDIRECT,
1409         }
1410
1411         def __dumpMem(self, prefix, memArray, maxLen):
1412                 if not memArray:
1413                         return prefix + "--"
1414                 ret, line, first, count, i = [], [], True, 0, 0
1415                 while i < maxLen:
1416                         line.append("%02X" % memArray[i])
1417                         count += 1
1418                         if count >= 16:
1419                                 if not first:
1420                                         prefix = ' ' * len(prefix)
1421                                 first = False
1422                                 ret.append(prefix + ' '.join(line))
1423                                 line, count = [], 0
1424                         i += 1
1425                 assert(count == 0)
1426                 return '\n'.join(ret)
1427
1428         def __repr__(self):
1429                 if not self.callStack:
1430                         return ""
1431                 mnemonics = self.specs.getMnemonics()
1432                 isEnglish = (mnemonics == S7CPUSpecs.MNEMONICS_EN)
1433                 self.updateTimestamp()
1434                 ret = []
1435                 ret.append("=== S7-CPU dump ===  (t: %.01fs)" %\
1436                            (self.now - self.startupTime))
1437                 ret.append("    STW:  " + self.statusWord.getString(mnemonics))
1438                 if self.is4accu:
1439                         accus = [ accu.toHex()
1440                                   for accu in (self.accu1, self.accu2,
1441                                                self.accu3, self.accu4) ]
1442                 else:
1443                         accus = [ accu.toHex()
1444                                   for accu in (self.accu1, self.accu2) ]
1445                 ret.append("   Accu:  " + "  ".join(accus))
1446                 ars = [ "%s (%s)" % (ar.toHex(), ar.toPointerString())
1447                         for ar in (self.ar1, self.ar2) ]
1448                 ret.append("     AR:  " + "  ".join(ars))
1449                 ret.append(self.__dumpMem("      M:  ",
1450                                           self.flags,
1451                                           min(64, self.specs.nrFlags)))
1452                 prefix = "      I:  " if isEnglish else "      E:  "
1453                 ret.append(self.__dumpMem(prefix,
1454                                           self.inputs,
1455                                           min(64, self.specs.nrInputs)))
1456                 prefix = "      Q:  " if isEnglish else "      A:  "
1457                 ret.append(self.__dumpMem(prefix,
1458                                           self.outputs,
1459                                           min(64, self.specs.nrOutputs)))
1460                 pstack = str(self.callStackTop.parenStack) if self.callStackTop.parenStack else "Empty"
1461                 ret.append(" PStack:  " + pstack)
1462                 ret.append("     DB:  %s" % str(self.dbRegister))
1463                 ret.append("     DI:  %s" % str(self.diRegister))
1464                 if self.callStack:
1465                         elems = [ str(cse) for cse in self.callStack ]
1466                         elems = " => ".join(elems)
1467                         ret.append("  Calls:  depth:%d   %s" %\
1468                                    (len(self.callStack), elems))
1469                         localdata = self.callStack[-1].localdata
1470                         ret.append(self.__dumpMem("      L:  ",
1471                                                   localdata,
1472                                                   min(16, self.specs.nrLocalbytes)))
1473                         try:
1474                                 localdata = self.callStack[-2].localdata
1475                         except IndexError:
1476                                 localdata = None
1477                         ret.append(self.__dumpMem("     VL:  ",
1478                                                   localdata,
1479                                                   min(16, self.specs.nrLocalbytes)))
1480                 else:
1481                         ret.append("  Calls:  None")
1482                 curInsn = self.getCurrentInsn()
1483                 ret.append("   Stmt:  IP:%s   %s" %\
1484                            (str(self.getCurrentIP()),
1485                             str(curInsn) if curInsn else ""))
1486                 ret.append("  Speed:  %d stmt/s  %.01f stmt/cycle" %\
1487                            (int(round(self.insnPerSecond)),
1488                             self.avgInsnPerCycle))
1489                 ret.append(" CycleT:  avg:%.06fs  min:%.06fs  max:%.06fs" %\
1490                            (self.avgCycleTime, self.minCycleTime,
1491                             self.maxCycleTime))
1492                 return '\n'.join(ret)