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