gui: Enable extended instructions via menu item
[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.findByName(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.findByName(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.findByName(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 = Accu(), Accu()
549                         if self.specs.nrAccus == 2:
550                                 self.accu3, self.accu4 = None, None
551                         elif self.specs.nrAccus == 4:
552                                 self.accu3, self.accu4 = Accu(), Accu()
553                         else:
554                                 assert(0)
555                 if force or self.specs.nrTimers != len(self.timers):
556                         self.timers = [ Timer(self, i)
557                                         for i in range(self.specs.nrTimers) ]
558                 if force or self.specs.nrCounters != len(self.counters):
559                         self.counters = [ Counter(self, i)
560                                           for i in range(self.specs.nrCounters) ]
561                 if force or self.specs.nrFlags != len(self.flags):
562                         self.flags = ByteArray(self.specs.nrFlags)
563                 if force or self.specs.nrInputs != len(self.inputs):
564                         self.inputs = ByteArray(self.specs.nrInputs)
565                 if force or self.specs.nrOutputs != len(self.outputs):
566                         self.outputs = ByteArray(self.specs.nrOutputs)
567                 CallStackElem.resetCache()
568
569         def reset(self):
570                 self.dbs = {
571                         # DBs
572                         0 : DB(0, permissions = 0), # read/write-protected system-DB
573                 }
574                 self.obs = {
575                         # OBs
576                 }
577                 self.obTempPresetHandlers = {
578                         # OB TEMP-preset handlers
579                 }
580                 self.fcs = {
581                         # User FCs
582                 }
583                 self.fbs = {
584                         # User FBs
585                 }
586                 self.sfcs = {
587                         # System SFCs
588                 }
589                 self.sfbs = {
590                         # System SFBs
591                 }
592                 self.symbolTable = SymbolTable()
593                 self.reallocate(force=True)
594                 self.ar1 = Adressregister()
595                 self.ar2 = Adressregister()
596                 self.dbRegister = self.dbs[0]
597                 self.diRegister = self.dbs[0]
598                 self.callStack = [ ]
599                 self.callStackTop = None
600                 self.setMcrActive(False)
601                 self.mcrStack = [ ]
602                 self.statusWord = S7StatusWord()
603
604                 self.relativeJump = 1
605
606                 # Stats
607                 self.__insnCount = 0
608                 self.__cycleCount = 0
609                 self.insnPerSecond = 0.0
610                 self.avgInsnPerCycle = 0.0
611                 self.cycleStartTime = 0.0
612                 self.minCycleTime = 86400.0
613                 self.maxCycleTime = 0.0
614                 self.avgCycleTime = 0.0
615                 self.startupTime = 0.0
616                 self.__speedMeasureStartTime = 0
617                 self.__speedMeasureStartInsnCount = 0
618                 self.__speedMeasureStartCycleCount = 0
619
620                 self.updateTimestamp()
621
622         def setCycleExitCallback(self, cb, data=None):
623                 self.cbCycleExit = cb
624                 self.cbCycleExitData = data
625
626         def setBlockExitCallback(self, cb, data=None):
627                 self.cbBlockExit = cb
628                 self.cbBlockExitData = data
629
630         def setPostInsnCallback(self, cb, data=None):
631                 self.cbPostInsn = cb
632                 self.cbPostInsnData = data
633
634         def setPeripheralReadCallback(self, cb, data=None):
635                 self.cbPeripheralRead = cb
636                 self.cbPeripheralReadData = data
637
638         def setPeripheralWriteCallback(self, cb, data=None):
639                 self.cbPeripheralWrite = cb
640                 self.cbPeripheralWriteData = data
641
642         def setScreenUpdateCallback(self, cb, data=None):
643                 self.cbScreenUpdate = cb
644                 self.cbScreenUpdateData = data
645
646         def requestScreenUpdate(self):
647                 if self.cbScreenUpdate:
648                         self.cbScreenUpdate(self.cbScreenUpdateData)
649
650         @property
651         def is4accu(self):
652                 return self.accu4 is not None
653
654         def __runOB(self, block):
655                 # Update timekeeping
656                 self.updateTimestamp()
657                 self.cycleStartTime = self.now
658
659                 # Initialize CPU state
660                 self.callStack = [ CallStackElem(self, block) ]
661                 self.dbRegister = self.diRegister = self.dbs[0]
662                 cse = self.callStackTop = self.callStack[-1]
663                 if self.__obTempPresetsEnabled:
664                         # Populate the TEMP region
665                         self.obTempPresetHandlers[block.index].generate(cse.localdata)
666
667                 # Run the user program cycle
668                 while self.callStack:
669                         while cse.ip < len(cse.insns):
670                                 insn, self.relativeJump = cse.insns[cse.ip], 1
671                                 insn.run()
672                                 if self.cbPostInsn:
673                                         self.cbPostInsn(self.cbPostInsnData)
674                                 cse.ip += self.relativeJump
675                                 cse, self.__insnCount = self.callStackTop,\
676                                                         (self.__insnCount + 1) & 0x3FFFFFFF
677                                 if not self.__insnCount % 64:
678                                         self.updateTimestamp()
679                                         self.__runTimeCheck()
680                         if self.cbBlockExit:
681                                 self.cbBlockExit(self.cbBlockExitData)
682                         prevCse = self.callStack.pop()
683                         if self.callStack:
684                                 cse = self.callStackTop = self.callStack[-1]
685                                 prevCse.handleBlockExit()
686                         prevCse.destroy()
687                 if self.cbCycleExit:
688                         self.cbCycleExit(self.cbCycleExitData)
689
690         # Run startup code
691         def startup(self):
692                 # Resolve symbolic instructions and operators
693                 self.__resolveSymbols()
694                 # Run some static sanity checks on the code
695                 self.__staticSanityChecks()
696
697                 self.updateTimestamp()
698                 self.__speedMeasureStartTime = self.now
699                 self.__speedMeasureStartInsnCount = 0
700                 self.__speedMeasureStartCycleCount = 0
701                 self.startupTime = self.now
702
703                 # Run startup OB
704                 if 102 in self.obs and self.is4accu:
705                         # Cold start.
706                         # This is only done on 4xx-series CPUs.
707                         self.__runOB(self.obs[102])
708                 elif 100 in self.obs:
709                         # Warm start.
710                         # This really is a cold start, because remanent
711                         # resources were reset. However we could not execute
712                         # OB 102, so this is a fallback.
713                         # This is not 100% compliant with real CPUs, but it probably
714                         # is sane behavior.
715                         self.__runOB(self.obs[100])
716
717         # Run one cycle of the user program
718         def runCycle(self):
719                 # Run the actual OB1 code
720                 self.__runOB(self.obs[1])
721
722                 # Update timekeeping and statistics
723                 self.updateTimestamp()
724                 self.__cycleCount = (self.__cycleCount + 1) & 0x3FFFFFFF
725
726                 # Evaluate speed measurement
727                 elapsedTime = self.now - self.__speedMeasureStartTime
728                 if elapsedTime >= 1.0:
729                         # Calculate instruction and cycle counts.
730                         cycleCount = (self.__cycleCount - self.__speedMeasureStartCycleCount) &\
731                                      0x3FFFFFFF
732                         insnCount = (self.__insnCount - self.__speedMeasureStartInsnCount) &\
733                                     0x3FFFFFFF
734
735                         # Calculate instruction statistics.
736                         self.insnPerSecond = insnCount / elapsedTime
737                         self.avgInsnPerCycle = insnCount / cycleCount
738
739                         # Get the average cycle time over the measurement period.
740                         cycleTime = elapsedTime / cycleCount
741
742                         # Store overall-average cycle time and maximum cycle time.
743                         self.maxCycleTime = max(self.maxCycleTime, cycleTime)
744                         self.minCycleTime = min(self.minCycleTime, cycleTime)
745                         self.avgCycleTime = (self.avgCycleTime + cycleTime) / 2.0
746
747                         # Reset the counters
748                         self.__speedMeasureStartTime = self.now
749                         self.__speedMeasureStartInsnCount = self.__insnCount
750                         self.__speedMeasureStartCycleCount = self.__cycleCount
751
752         def __updateTimestamp_perf(self):
753                 self.now = time.perf_counter()
754
755         def __updateTimestamp_time(self):
756                 self.now = time.time()
757
758         # Construct updateTimestamp() method.
759         # updateTimestamp() updates self.now, which is a
760         # floating point count of seconds.
761         if hasattr(time, "perf_counter"):
762                 updateTimestamp = __updateTimestamp_perf
763         else:
764                 updateTimestamp = __updateTimestamp_time
765
766         __dateAndTimeWeekdayMap = {
767                 0       : 2,    # monday
768                 1       : 3,    # tuesday
769                 2       : 4,    # wednesday
770                 3       : 5,    # thursday
771                 4       : 6,    # friday
772                 5       : 7,    # saturday
773                 6       : 1,    # sunday
774         }
775
776         # Make a DATE_AND_TIME for the current wall-time and
777         # store it in byteArray, which is a list of GenericByte objects.
778         # If byteArray is smaller than 8 bytes, an IndexError is raised.
779         def makeCurrentDateAndTime(self, byteArray, offset):
780                 dt = datetime.datetime.now()
781                 year, month, day, hour, minute, second, msec =\
782                         dt.year, dt.month, dt.day, dt.hour, \
783                         dt.minute, dt.second, dt.microsecond // 1000
784                 byteArray[offset] = (year % 10) | (((year // 10) % 10) << 4)
785                 byteArray[offset + 1] = (month % 10) | (((month // 10) % 10) << 4)
786                 byteArray[offset + 2] = (day % 10) | (((day // 10) % 10) << 4)
787                 byteArray[offset + 3] = (hour % 10) | (((hour // 10) % 10) << 4)
788                 byteArray[offset + 4] = (minute % 10) | (((minute // 10) % 10) << 4)
789                 byteArray[offset + 5] = (second % 10) | (((second // 10) % 10) << 4)
790                 byteArray[offset + 6] = ((msec // 10) % 10) | (((msec // 100) % 10) << 4)
791                 byteArray[offset + 7] = ((msec % 10) << 4) |\
792                                         self.__dateAndTimeWeekdayMap[dt.weekday()]
793
794         def __runTimeCheck(self):
795                 if self.now - self.cycleStartTime > self.cycleTimeLimit:
796                         raise AwlSimError("Cycle time exceed %.3f seconds" %\
797                                           self.cycleTimeLimit)
798
799         def getCurrentIP(self):
800                 try:
801                         return self.callStackTop.ip
802                 except IndexError as e:
803                         return None
804
805         def getCurrentInsn(self):
806                 try:
807                         cse = self.callStackTop
808                         if not cse:
809                                 return None
810                         return cse.insns[cse.ip]
811                 except IndexError as e:
812                         return None
813
814         def labelIdxToRelJump(self, labelIndex):
815                 # Translate a label index into a relative IP offset.
816                 cse = self.callStackTop
817                 label = cse.block.labels[labelIndex]
818                 return label.getInsn().getIP() - cse.ip
819
820         def jumpToLabel(self, labelIndex):
821                 self.relativeJump = self.labelIdxToRelJump(labelIndex)
822
823         def jumpRelative(self, insnOffset):
824                 self.relativeJump = insnOffset
825
826         def __call_FC(self, blockOper, dbOper, parameters):
827                 fc = self.fcs[blockOper.value.byteOffset]
828                 return CallStackElem(self, fc, None, parameters)
829
830         def __call_RAW_FC(self, blockOper, dbOper, parameters):
831                 fc = self.fcs[blockOper.value.byteOffset]
832                 return CallStackElem(self, fc, None, (), True)
833
834         def __call_FB(self, blockOper, dbOper, parameters):
835                 fb = self.fbs[blockOper.value.byteOffset]
836                 db = self.dbs[dbOper.value.byteOffset]
837                 cse = CallStackElem(self, fb, db, parameters)
838                 self.dbRegister, self.diRegister = self.diRegister, db
839                 return cse
840
841         def __call_RAW_FB(self, blockOper, dbOper, parameters):
842                 fb = self.fbs[blockOper.value.byteOffset]
843                 return CallStackElem(self, fb, self.diRegister, (), True)
844
845         def __call_SFC(self, blockOper, dbOper, parameters):
846                 sfc = self.sfcs[blockOper.value.byteOffset]
847                 return CallStackElem(self, sfc, None, parameters)
848
849         def __call_RAW_SFC(self, blockOper, dbOper, parameters):
850                 sfc = self.sfcs[blockOper.value.byteOffset]
851                 return CallStackElem(self, sfc, None, (), True)
852
853         def __call_SFB(self, blockOper, dbOper, parameters):
854                 sfb = self.sfbs[blockOper.value.byteOffset]
855                 db = self.dbs[dbOper.value.byteOffset]
856                 cse = CallStackElem(self, sfb, db, parameters)
857                 self.dbRegister, self.diRegister = self.diRegister, db
858                 return cse
859
860         def __call_RAW_SFB(self, blockOper, dbOper, parameters):
861                 sfb = self.sfbs[blockOper.value.byteOffset]
862                 return CallStackElem(self, sfb, self.diRegister, (), True)
863
864         def __call_INDIRECT(self, blockOper, dbOper, parameters):
865                 blockOper = blockOper.resolve()
866                 callHelper = self.__rawCallHelpers[blockOper.type]
867                 try:
868                         return callHelper(self, blockOper, dbOper, parameters)
869                 except KeyError as e:
870                         raise AwlSimError("Code block %d not found in indirect call" %\
871                                           blockOper.value.byteOffset)
872
873         __callHelpers = {
874                 AwlOperator.BLKREF_FC   : __call_FC,
875                 AwlOperator.BLKREF_FB   : __call_FB,
876                 AwlOperator.BLKREF_SFC  : __call_SFC,
877                 AwlOperator.BLKREF_SFB  : __call_SFB,
878         }
879
880         __rawCallHelpers = {
881                 AwlOperator.BLKREF_FC   : __call_RAW_FC,
882                 AwlOperator.BLKREF_FB   : __call_RAW_FB,
883                 AwlOperator.BLKREF_SFC  : __call_RAW_SFC,
884                 AwlOperator.BLKREF_SFB  : __call_RAW_SFB,
885                 AwlOperator.INDIRECT    : __call_INDIRECT,
886         }
887
888         def run_CALL(self, blockOper, dbOper=None, parameters=(), raw=False):
889                 try:
890                         if raw:
891                                 callHelper = self.__rawCallHelpers[blockOper.type]
892                         else:
893                                 callHelper = self.__callHelpers[blockOper.type]
894                 except KeyError:
895                         raise AwlSimError("Invalid CALL operand")
896                 newCse = callHelper(self, blockOper, dbOper, parameters)
897                 if newCse:
898                         self.callStack.append(newCse)
899                         self.callStackTop = newCse
900
901         def run_BE(self):
902                 s = self.statusWord
903                 s.OS, s.OR, s.STA, s.NER = 0, 0, 1, 0
904                 # Jump beyond end of block
905                 cse = self.callStackTop
906                 self.relativeJump = len(cse.insns) - cse.ip
907
908         def run_AUF(self, dbOper):
909                 dbOper = dbOper.resolve()
910                 try:
911                         db = self.dbs[dbOper.value.byteOffset]
912                 except KeyError:
913                         raise AwlSimError("Datablock %i does not exist" %\
914                                           dbOper.value.byteOffset)
915                 if dbOper.type == AwlOperator.BLKREF_DB:
916                         self.dbRegister = db
917                 elif dbOper.type == AwlOperator.BLKREF_DI:
918                         self.diRegister = db
919                 else:
920                         raise AwlSimError("Invalid DB reference in AUF")
921
922         def run_TDB(self):
923                 # Swap global and instance DB
924                 self.diRegister, self.dbRegister = self.dbRegister, self.diRegister
925
926         def getStatusWord(self):
927                 return self.statusWord
928
929         def getAccu(self, index):
930                 if index < 1 or index > self.specs.nrAccus:
931                         raise AwlSimError("Invalid ACCU offset")
932                 return (self.accu1, self.accu2,
933                         self.accu3, self.accu4)[index - 1]
934
935         def getAR(self, index):
936                 if index < 1 or index > 2:
937                         raise AwlSimError("Invalid AR offset")
938                 return (self.ar1, self.ar2)[index - 1]
939
940         def getTimer(self, index):
941                 try:
942                         return self.timers[index]
943                 except IndexError as e:
944                         raise AwlSimError("Fetched invalid timer %d" % index)
945
946         def getCounter(self, index):
947                 try:
948                         return self.counters[index]
949                 except IndexError as e:
950                         raise AwlSimError("Fetched invalid counter %d" % index)
951
952         def getSpecs(self):
953                 return self.specs
954
955         def setMcrActive(self, active):
956                 self.mcrActive = active
957
958         def mcrIsOn(self):
959                 return (not self.mcrActive or all(self.mcrStack))
960
961         def mcrStackAppend(self, statusWord):
962                 self.mcrStack.append(McrStackElem(statusWord))
963                 if len(self.mcrStack) > 8:
964                         raise AwlSimError("MCR stack overflow")
965
966         def mcrStackPop(self):
967                 try:
968                         return self.mcrStack.pop()
969                 except IndexError:
970                         raise AwlSimError("MCR stack underflow")
971
972         def parenStackAppend(self, insnType, statusWord):
973                 cse = self.callStackTop
974                 cse.parenStack.append(ParenStackElem(self, insnType, statusWord))
975                 if len(cse.parenStack) > 7:
976                         raise AwlSimError("Parenthesis stack overflow")
977
978         # Fetch a range in the 'output' memory area.
979         # 'byteOffset' is the byte offset into the output area.
980         # 'byteCount' is the number if bytes to fetch.
981         # Returns a bytearray.
982         def fetchOutputRange(self, byteOffset, byteCount):
983                 return self.outputs[byteOffset : byteOffset + byteCount]
984
985         # Store a range in the 'input' memory area.
986         # 'byteOffset' is the byte offset into the input area.
987         # 'data' is a bytearray.
988         def storeInputRange(self, byteOffset, data):
989                 self.inputs[byteOffset : byteOffset + len(data)] = data
990
991         def fetch(self, operator, enforceWidth=()):
992                 try:
993                         fetchMethod = self.fetchTypeMethods[operator.type]
994                 except KeyError:
995                         raise AwlSimError("Invalid fetch request: %s" %\
996                                 AwlOperator.type2str[operator.type])
997                 return fetchMethod(self, operator, enforceWidth)
998
999         def __fetchWidthError(self, operator, enforceWidth):
1000                 raise AwlSimError("Data fetch of %d bits, "
1001                         "but only %s bits are allowed." %\
1002                         (operator.width,
1003                          listToHumanStr(enforceWidth)))
1004
1005         def fetchIMM(self, operator, enforceWidth):
1006                 if operator.width not in enforceWidth and enforceWidth:
1007                         self.__fetchWidthError(operator, enforceWidth)
1008
1009                 return operator.value
1010
1011         def fetchDBLG(self, operator, enforceWidth):
1012                 if operator.width not in enforceWidth and enforceWidth:
1013                         self.__fetchWidthError(operator, enforceWidth)
1014
1015                 return self.dbRegister.struct.getSize()
1016
1017         def fetchDBNO(self, operator, enforceWidth):
1018                 if operator.width not in enforceWidth and enforceWidth:
1019                         self.__fetchWidthError(operator, enforceWidth)
1020
1021                 return self.dbRegister.index
1022
1023         def fetchDILG(self, operator, enforceWidth):
1024                 if operator.width not in enforceWidth and enforceWidth:
1025                         self.__fetchWidthError(operator, enforceWidth)
1026
1027                 return self.diRegister.struct.getSize()
1028
1029         def fetchDINO(self, operator, enforceWidth):
1030                 if operator.width not in enforceWidth and enforceWidth:
1031                         self.__fetchWidthError(operator, enforceWidth)
1032
1033                 return self.diRegister.index
1034
1035         def fetchAR2(self, operator, enforceWidth):
1036                 if operator.width not in enforceWidth and enforceWidth:
1037                         self.__fetchWidthError(operator, enforceWidth)
1038
1039                 return self.getAR(2).get()
1040
1041         def fetchSTW(self, operator, enforceWidth):
1042                 if operator.width not in enforceWidth and enforceWidth:
1043                         self.__fetchWidthError(operator, enforceWidth)
1044
1045                 if operator.width == 1:
1046                         return self.statusWord.getByBitNumber(operator.value.bitOffset)
1047                 elif operator.width == 16:
1048                         return self.statusWord.getWord()
1049                 else:
1050                         assert(0)
1051
1052         def fetchSTW_Z(self, operator, enforceWidth):
1053                 if operator.width not in enforceWidth and enforceWidth:
1054                         self.__fetchWidthError(operator, enforceWidth)
1055
1056                 return (self.statusWord.A0 ^ 1) & (self.statusWord.A1 ^ 1)
1057
1058         def fetchSTW_NZ(self, operator, enforceWidth):
1059                 if operator.width not in enforceWidth and enforceWidth:
1060                         self.__fetchWidthError(operator, enforceWidth)
1061
1062                 return self.statusWord.A0 | self.statusWord.A1
1063
1064         def fetchSTW_POS(self, operator, enforceWidth):
1065                 if operator.width not in enforceWidth and enforceWidth:
1066                         self.__fetchWidthError(operator, enforceWidth)
1067
1068                 return (self.statusWord.A0 ^ 1) & self.statusWord.A1
1069
1070         def fetchSTW_NEG(self, operator, enforceWidth):
1071                 if operator.width not in enforceWidth and enforceWidth:
1072                         self.__fetchWidthError(operator, enforceWidth)
1073
1074                 return self.statusWord.A0 & (self.statusWord.A1 ^ 1)
1075
1076         def fetchSTW_POSZ(self, operator, enforceWidth):
1077                 if operator.width not in enforceWidth and enforceWidth:
1078                         self.__fetchWidthError(operator, enforceWidth)
1079
1080                 return self.statusWord.A0 ^ 1
1081
1082         def fetchSTW_NEGZ(self, operator, enforceWidth):
1083                 if operator.width not in enforceWidth and enforceWidth:
1084                         self.__fetchWidthError(operator, enforceWidth)
1085
1086                 return self.statusWord.A1 ^ 1
1087
1088         def fetchSTW_UO(self, operator, enforceWidth):
1089                 if operator.width not in enforceWidth and enforceWidth:
1090                         self.__fetchWidthError(operator, enforceWidth)
1091
1092                 return self.statusWord.A0 & self.statusWord.A1
1093
1094         def fetchE(self, operator, enforceWidth):
1095                 if operator.width not in enforceWidth and enforceWidth:
1096                         self.__fetchWidthError(operator, enforceWidth)
1097
1098                 return self.inputs.fetch(operator.value, operator.width)
1099
1100         def fetchA(self, operator, enforceWidth):
1101                 if operator.width not in enforceWidth and enforceWidth:
1102                         self.__fetchWidthError(operator, enforceWidth)
1103
1104                 return self.outputs.fetch(operator.value, operator.width)
1105
1106         def fetchM(self, operator, enforceWidth):
1107                 if operator.width not in enforceWidth and enforceWidth:
1108                         self.__fetchWidthError(operator, enforceWidth)
1109
1110                 return self.flags.fetch(operator.value, operator.width)
1111
1112         def fetchL(self, operator, enforceWidth):
1113                 if operator.width not in enforceWidth and enforceWidth:
1114                         self.__fetchWidthError(operator, enforceWidth)
1115
1116                 return self.callStackTop.localdata.fetch(operator.value, operator.width)
1117
1118         def fetchVL(self, operator, enforceWidth):
1119                 if operator.width not in enforceWidth and enforceWidth:
1120                         self.__fetchWidthError(operator, enforceWidth)
1121
1122                 try:
1123                         cse = self.callStack[-2]
1124                 except IndexError:
1125                         raise AwlSimError("Fetch of parent localstack, "
1126                                 "but no parent present.")
1127                 return cse.localdata.fetch(operator.value, operator.width)
1128
1129         def fetchDB(self, operator, enforceWidth):
1130                 if operator.width not in enforceWidth and enforceWidth:
1131                         self.__fetchWidthError(operator, enforceWidth)
1132
1133                 if operator.value.dbNumber is not None:
1134                         # This is a fully qualified access (DBx.DBx X)
1135                         # Open the data block first.
1136                         self.run_AUF(AwlOperator(AwlOperator.BLKREF_DB, 16,
1137                                                  AwlOffset(operator.value.dbNumber),
1138                                                  operator.insn))
1139                 if not self.dbRegister:
1140                         raise AwlSimError("Fetch from global DB, "
1141                                 "but no DB is opened")
1142                 return self.dbRegister.fetch(operator)
1143
1144         def fetchDI(self, operator, enforceWidth):
1145                 if operator.width not in enforceWidth and enforceWidth:
1146                         self.__fetchWidthError(operator, enforceWidth)
1147
1148                 if not self.diRegister:
1149                         raise AwlSimError("Fetch from instance DI, "
1150                                 "but no DI is opened")
1151                 return self.diRegister.fetch(operator)
1152
1153         def fetchPE(self, operator, enforceWidth):
1154                 if operator.width not in enforceWidth and enforceWidth:
1155                         self.__fetchWidthError(operator, enforceWidth)
1156
1157                 value = None
1158                 if self.cbPeripheralRead:
1159                         value = self.cbPeripheralRead(self.cbPeripheralReadData,
1160                                                       operator.width,
1161                                                       operator.value.byteOffset)
1162                 if value is None:
1163                         raise AwlSimError("There is no hardware to handle "
1164                                 "the direct peripheral fetch. "
1165                                 "(width=%d, offset=%d)" %\
1166                                 (operator.width, operator.value.byteOffset))
1167                 self.inputs.store(operator.value, operator.width, value)
1168                 return self.inputs.fetch(operator.value, operator.width)
1169
1170         def fetchT(self, operator, enforceWidth):
1171                 insnType = operator.insn.type
1172                 if insnType == AwlInsn.TYPE_L or insnType == AwlInsn.TYPE_LC:
1173                         width = 32
1174                 else:
1175                         width = 1
1176                 if width not in enforceWidth and enforceWidth:
1177                         self.__fetchWidthError(operator, enforceWidth)
1178
1179                 timer = self.getTimer(operator.value.byteOffset)
1180                 if insnType == AwlInsn.TYPE_L:
1181                         return timer.getTimevalBin()
1182                 elif insnType == AwlInsn.TYPE_LC:
1183                         return timer.getTimevalS5T()
1184                 return timer.get()
1185
1186         def fetchZ(self, operator, enforceWidth):
1187                 insnType = operator.insn.type
1188                 if insnType == AwlInsn.TYPE_L or insnType == AwlInsn.TYPE_LC:
1189                         width = 32
1190                 else:
1191                         width = 1
1192                 if width not in enforceWidth and enforceWidth:
1193                         self.__fetchWidthError(operator, enforceWidth)
1194
1195                 counter = self.getCounter(operator.value.byteOffset)
1196                 if insnType == AwlInsn.TYPE_L:
1197                         return counter.getValueBin()
1198                 elif insnType == AwlInsn.TYPE_LC:
1199                         return counter.getValueBCD()
1200                 return counter.get()
1201
1202         def fetchNAMED_LOCAL(self, operator, enforceWidth):
1203                 # load from an FC interface field.
1204                 return self.fetch(self.callStackTop.interfRefs[operator.interfaceIndex].resolve(False),
1205                                   enforceWidth)
1206
1207         def fetchNAMED_LOCAL_PTR(self, operator, enforceWidth):
1208                 return self.callStackTop.interfRefs[operator.interfaceIndex].resolve(False).makePointer()
1209
1210         def fetchNAMED_DBVAR(self, operator, enforceWidth):
1211                 # All legit accesses will have been translated to absolute addressing already
1212                 raise AwlSimError("Fully qualified load from DB variable "
1213                         "is not supported in this place.")
1214
1215         def fetchINDIRECT(self, operator, enforceWidth):
1216                 return self.fetch(operator.resolve(False), enforceWidth)
1217
1218         def fetchVirtACCU(self, operator, enforceWidth):
1219                 if operator.width not in enforceWidth and enforceWidth:
1220                         self.__fetchWidthError(operator, enforceWidth)
1221
1222                 return self.getAccu(operator.value.byteOffset).get()
1223
1224         def fetchVirtAR(self, operator, enforceWidth):
1225                 if operator.width not in enforceWidth and enforceWidth:
1226                         self.__fetchWidthError(operator, enforceWidth)
1227
1228                 return self.getAR(operator.value.byteOffset).get()
1229
1230         def fetchVirtDBR(self, operator, enforceWidth):
1231                 if operator.width not in enforceWidth and enforceWidth:
1232                         self.__fetchWidthError(operator, enforceWidth)
1233
1234                 if operator.value.byteOffset == 1:
1235                         if self.dbRegister:
1236                                 return self.dbRegister.index
1237                 elif operator.value.byteOffset == 2:
1238                         if self.diRegister:
1239                                 return self.diRegister.index
1240                 else:
1241                         raise AwlSimError("Invalid __DBR %d. "
1242                                 "Must be 1 for DB-register or "
1243                                 "2 for DI-register." %\
1244                                 operator.value.byteOffset)
1245                 return 0
1246
1247         fetchTypeMethods = {
1248                 AwlOperator.IMM                 : fetchIMM,
1249                 AwlOperator.IMM_REAL            : fetchIMM,
1250                 AwlOperator.IMM_S5T             : fetchIMM,
1251                 AwlOperator.IMM_TIME            : fetchIMM,
1252                 AwlOperator.IMM_DATE            : fetchIMM,
1253                 AwlOperator.IMM_TOD             : fetchIMM,
1254                 AwlOperator.IMM_PTR             : fetchIMM,
1255                 AwlOperator.MEM_E               : fetchE,
1256                 AwlOperator.MEM_A               : fetchA,
1257                 AwlOperator.MEM_M               : fetchM,
1258                 AwlOperator.MEM_L               : fetchL,
1259                 AwlOperator.MEM_VL              : fetchVL,
1260                 AwlOperator.MEM_DB              : fetchDB,
1261                 AwlOperator.MEM_DI              : fetchDI,
1262                 AwlOperator.MEM_T               : fetchT,
1263                 AwlOperator.MEM_Z               : fetchZ,
1264                 AwlOperator.MEM_PE              : fetchPE,
1265                 AwlOperator.MEM_DBLG            : fetchDBLG,
1266                 AwlOperator.MEM_DBNO            : fetchDBNO,
1267                 AwlOperator.MEM_DILG            : fetchDILG,
1268                 AwlOperator.MEM_DINO            : fetchDINO,
1269                 AwlOperator.MEM_AR2             : fetchAR2,
1270                 AwlOperator.MEM_STW             : fetchSTW,
1271                 AwlOperator.MEM_STW_Z           : fetchSTW_Z,
1272                 AwlOperator.MEM_STW_NZ          : fetchSTW_NZ,
1273                 AwlOperator.MEM_STW_POS         : fetchSTW_POS,
1274                 AwlOperator.MEM_STW_NEG         : fetchSTW_NEG,
1275                 AwlOperator.MEM_STW_POSZ        : fetchSTW_POSZ,
1276                 AwlOperator.MEM_STW_NEGZ        : fetchSTW_NEGZ,
1277                 AwlOperator.MEM_STW_UO          : fetchSTW_UO,
1278                 AwlOperator.NAMED_LOCAL         : fetchNAMED_LOCAL,
1279                 AwlOperator.NAMED_LOCAL_PTR     : fetchNAMED_LOCAL_PTR,
1280                 AwlOperator.NAMED_DBVAR         : fetchNAMED_DBVAR,
1281                 AwlOperator.INDIRECT            : fetchINDIRECT,
1282                 AwlOperator.VIRT_ACCU           : fetchVirtACCU,
1283                 AwlOperator.VIRT_AR             : fetchVirtAR,
1284                 AwlOperator.VIRT_DBR            : fetchVirtDBR,
1285         }
1286
1287         def store(self, operator, value, enforceWidth=()):
1288                 try:
1289                         storeMethod = self.storeTypeMethods[operator.type]
1290                 except KeyError:
1291                         raise AwlSimError("Invalid store request")
1292                 storeMethod(self, operator, value, enforceWidth)
1293
1294         def __storeWidthError(self, operator, enforceWidth):
1295                 raise AwlSimError("Data store of %d bits, "
1296                         "but only %s bits are allowed." %\
1297                         (operator.width,
1298                          listToHumanStr(enforceWidth)))
1299
1300         def storeE(self, operator, value, enforceWidth):
1301                 if operator.width not in enforceWidth and enforceWidth:
1302                         self.__storeWidthError(operator, enforceWidth)
1303
1304                 self.inputs.store(operator.value, operator.width, value)
1305
1306         def storeA(self, operator, value, enforceWidth):
1307                 if operator.width not in enforceWidth and enforceWidth:
1308                         self.__storeWidthError(operator, enforceWidth)
1309
1310                 self.outputs.store(operator.value, operator.width, value)
1311
1312         def storeM(self, operator, value, enforceWidth):
1313                 if operator.width not in enforceWidth and enforceWidth:
1314                         self.__storeWidthError(operator, enforceWidth)
1315
1316                 self.flags.store(operator.value, operator.width, value)
1317
1318         def storeL(self, operator, value, enforceWidth):
1319                 if operator.width not in enforceWidth and enforceWidth:
1320                         self.__storeWidthError(operator, enforceWidth)
1321
1322                 self.callStackTop.localdata.store(operator.value, operator.width, value)
1323
1324         def storeVL(self, operator, value, enforceWidth):
1325                 if operator.width not in enforceWidth and enforceWidth:
1326                         self.__storeWidthError(operator, enforceWidth)
1327
1328                 try:
1329                         cse = self.callStack[-2]
1330                 except IndexError:
1331                         raise AwlSimError("Store to parent localstack, "
1332                                 "but no parent present.")
1333                 cse.localdata.store(operator.value, operator.width, value)
1334
1335         def storeDB(self, operator, value, enforceWidth):
1336                 if operator.width not in enforceWidth and enforceWidth:
1337                         self.__storeWidthError(operator, enforceWidth)
1338
1339                 if operator.value.dbNumber is None:
1340                         db = self.dbRegister
1341                         if not db:
1342                                 raise AwlSimError("Store to global DB, "
1343                                         "but no DB is opened")
1344                 else:
1345                         try:
1346                                 db = self.dbs[operator.value.dbNumber]
1347                         except KeyError:
1348                                 raise AwlSimError("Store to DB %d, but DB "
1349                                         "does not exist" % operator.value.dbNumber)
1350                 db.store(operator, value)
1351
1352         def storeDI(self, operator, value, enforceWidth):
1353                 if operator.width not in enforceWidth and enforceWidth:
1354                         self.__storeWidthError(operator, enforceWidth)
1355
1356                 if not self.diRegister:
1357                         raise AwlSimError("Store to instance DI, "
1358                                 "but no DI is opened")
1359                 self.diRegister.store(operator, value)
1360
1361         def storePA(self, operator, value, enforceWidth):
1362                 if operator.width not in enforceWidth and enforceWidth:
1363                         self.__storeWidthError(operator, enforceWidth)
1364
1365                 self.outputs.store(operator.value, operator.width, value)
1366                 ok = False
1367                 if self.cbPeripheralWrite:
1368                         ok = self.cbPeripheralWrite(self.cbPeripheralWriteData,
1369                                                     operator.width,
1370                                                     operator.value.byteOffset,
1371                                                     value)
1372                 if not ok:
1373                         raise AwlSimError("There is no hardware to handle "
1374                                 "the direct peripheral store. "
1375                                 "(width=%d, offset=%d, value=0x%X)" %\
1376                                 (operator.width, operator.value.byteOffset,
1377                                  value))
1378
1379         def storeAR2(self, operator, value, enforceWidth):
1380                 if operator.width not in enforceWidth and enforceWidth:
1381                         self.__storeWidthError(operator, enforceWidth)
1382
1383                 self.getAR(2).set(value)
1384
1385         def storeSTW(self, operator, value, enforceWidth):
1386                 if operator.width not in enforceWidth and enforceWidth:
1387                         self.__storeWidthError(operator, enforceWidth)
1388
1389                 if operator.width == 1:
1390                         raise AwlSimError("Cannot store to individual STW bits")
1391                 elif operator.width == 16:
1392                         self.statusWord.setWord(value)
1393                 else:
1394                         assert(0)
1395
1396         def storeNAMED_LOCAL(self, operator, value, enforceWidth):
1397                 # store to an FC interface field.
1398                 self.store(self.callStackTop.interfRefs[operator.interfaceIndex].resolve(True),
1399                            value, enforceWidth)
1400
1401         def storeNAMED_DBVAR(self, operator, value, enforceWidth):
1402                 # All legit accesses will have been translated to absolute addressing already
1403                 raise AwlSimError("Fully qualified store to DB variable "
1404                         "is not supported in this place.")
1405
1406         def storeINDIRECT(self, operator, value, enforceWidth):
1407                 self.store(operator.resolve(True), value, enforceWidth)
1408
1409         storeTypeMethods = {
1410                 AwlOperator.MEM_E               : storeE,
1411                 AwlOperator.MEM_A               : storeA,
1412                 AwlOperator.MEM_M               : storeM,
1413                 AwlOperator.MEM_L               : storeL,
1414                 AwlOperator.MEM_VL              : storeVL,
1415                 AwlOperator.MEM_DB              : storeDB,
1416                 AwlOperator.MEM_DI              : storeDI,
1417                 AwlOperator.MEM_PA              : storePA,
1418                 AwlOperator.MEM_AR2             : storeAR2,
1419                 AwlOperator.MEM_STW             : storeSTW,
1420                 AwlOperator.NAMED_LOCAL         : storeNAMED_LOCAL,
1421                 AwlOperator.NAMED_DBVAR         : storeNAMED_DBVAR,
1422                 AwlOperator.INDIRECT            : storeINDIRECT,
1423         }
1424
1425         def __dumpMem(self, prefix, memArray, maxLen):
1426                 if not memArray:
1427                         return prefix + "--"
1428                 ret, line, first, count, i = [], [], True, 0, 0
1429                 while i < maxLen:
1430                         line.append("%02X" % memArray[i])
1431                         count += 1
1432                         if count >= 16:
1433                                 if not first:
1434                                         prefix = ' ' * len(prefix)
1435                                 first = False
1436                                 ret.append(prefix + ' '.join(line))
1437                                 line, count = [], 0
1438                         i += 1
1439                 assert(count == 0)
1440                 return '\n'.join(ret)
1441
1442         def __repr__(self):
1443                 if not self.callStack:
1444                         return ""
1445                 mnemonics = self.specs.getMnemonics()
1446                 isEnglish = (mnemonics == S7CPUSpecs.MNEMONICS_EN)
1447                 self.updateTimestamp()
1448                 ret = []
1449                 ret.append("=== S7-CPU dump ===  (t: %.01fs)" %\
1450                            (self.now - self.startupTime))
1451                 ret.append("    STW:  " + self.statusWord.getString(mnemonics))
1452                 if self.is4accu:
1453                         accus = [ accu.toHex()
1454                                   for accu in (self.accu1, self.accu2,
1455                                                self.accu3, self.accu4) ]
1456                 else:
1457                         accus = [ accu.toHex()
1458                                   for accu in (self.accu1, self.accu2) ]
1459                 ret.append("   Accu:  " + "  ".join(accus))
1460                 ars = [ "%s (%s)" % (ar.toHex(), ar.toPointerString())
1461                         for ar in (self.ar1, self.ar2) ]
1462                 ret.append("     AR:  " + "  ".join(ars))
1463                 ret.append(self.__dumpMem("      M:  ",
1464                                           self.flags,
1465                                           min(64, self.specs.nrFlags)))
1466                 prefix = "      I:  " if isEnglish else "      E:  "
1467                 ret.append(self.__dumpMem(prefix,
1468                                           self.inputs,
1469                                           min(64, self.specs.nrInputs)))
1470                 prefix = "      Q:  " if isEnglish else "      A:  "
1471                 ret.append(self.__dumpMem(prefix,
1472                                           self.outputs,
1473                                           min(64, self.specs.nrOutputs)))
1474                 pstack = str(self.callStackTop.parenStack) if self.callStackTop.parenStack else "Empty"
1475                 ret.append(" PStack:  " + pstack)
1476                 ret.append("     DB:  %s" % str(self.dbRegister))
1477                 ret.append("     DI:  %s" % str(self.diRegister))
1478                 if self.callStack:
1479                         elems = [ str(cse) for cse in self.callStack ]
1480                         elems = " => ".join(elems)
1481                         ret.append("  Calls:  depth:%d   %s" %\
1482                                    (len(self.callStack), elems))
1483                         localdata = self.callStack[-1].localdata
1484                         ret.append(self.__dumpMem("      L:  ",
1485                                                   localdata,
1486                                                   min(16, self.specs.nrLocalbytes)))
1487                         try:
1488                                 localdata = self.callStack[-2].localdata
1489                         except IndexError:
1490                                 localdata = None
1491                         ret.append(self.__dumpMem("     VL:  ",
1492                                                   localdata,
1493                                                   min(16, self.specs.nrLocalbytes)))
1494                 else:
1495                         ret.append("  Calls:  None")
1496                 curInsn = self.getCurrentInsn()
1497                 ret.append("   Stmt:  IP:%s   %s" %\
1498                            (str(self.getCurrentIP()),
1499                             str(curInsn) if curInsn else ""))
1500                 ret.append("  Speed:  %d stmt/s  %.01f stmt/cycle" %\
1501                            (int(round(self.insnPerSecond)),
1502                             self.avgInsnPerCycle))
1503                 ret.append(" CycleT:  avg:%.06fs  min:%.06fs  max:%.06fs" %\
1504                            (self.avgCycleTime, self.minCycleTime,
1505                             self.maxCycleTime))
1506                 return '\n'.join(ret)