Commit c48b62eb authored by Michael Büsch's avatar Michael Büsch

Add support for multiinstance calls

Signed-off-by: Michael Büsch's avatarMichael Buesch <m@bues.ch>
parent 9b148913
=== Incomplete awlsim TODO list ===
S7 compatibility:
- Add support for STRUCTs (This requires the removal of awlsim's newline-implies-semicolon semantics in headers)
- Add support for user defined data types (UDT)
- Add support for STRUCTs
- Add support for symbolic access to user defined data type (UDT) elements
- Implement parsing of attributes
- Implement POINTER and DB-pointer immediates
- Implement ANY pointer
- Add support for multiinstance calls
GUI:
- Make font size configurable (and save it in project file)
......
......@@ -2,7 +2,7 @@
#
# AWL simulator - CPU call stack
#
# Copyright 2012-2013 Michael Buesch <m@bues.ch>
# Copyright 2012-2014 Michael Buesch <m@bues.ch>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -41,12 +41,16 @@ class CallStackElem(object):
def resetCache(cls):
cls.lallocCache.reset()
def __init__(self, cpu, block, instanceDB=None, parameters=(),
def __init__(self, cpu, block,
instanceDB=None, instanceBaseOffset=None,
parameters=(),
isRawCall=False):
# Init the call stack element.
# cpu -> The CPU this runs on.
# block -> The code block that is being called.
# instanceDB -> The instance-DB, if FB-call. Otherwise None.
# instanceBaseOffset -> AwlOffset for use as AR2 instance base (multi-instance).
# If None, AR2 is not modified.
# parameters -> An iterable of AwlParamAssign instances
# representing the parameter assignments in CALL insn.
# isRawCall -> True, if the calling instruction was UC or CC.
......@@ -59,11 +63,17 @@ class CallStackElem(object):
self.instanceDB = instanceDB
self.prevDbRegister = cpu.dbRegister
self.prevDiRegister = cpu.diRegister
self.prevAR2value = cpu.ar2.get()
self.lalloc = self.lallocCache.get(cpu)
self.lalloc.allocation = block.interface.tempAllocation
self.localdata = self.lalloc.localdata
# Set AR2 to the specified multi-instance base
# and save the old AR2 value.
self.prevAR2value = cpu.ar2.get()
if instanceBaseOffset is not None:
cpu.ar2.set(AwlIndirectOp.AREA_DB |\
instanceBaseOffset.toPointerValue())
# Handle parameters
self.__outboundParams = []
if parameters and not isRawCall:
......@@ -86,7 +96,8 @@ class CallStackElem(object):
data = param.rvalueOp.resolve().value.byteOffset
else:
data = cpu.fetch(param.rvalueOp)
structInstance.setFieldData(structField, data)
structInstance.setFieldData(structField, data,
instanceBaseOffset)
else:
# This is a call to an FC.
# Prepare the interface (IN/OUT/INOUT) references.
......@@ -210,23 +221,31 @@ class CallStackElem(object):
def handleBlockExit(self):
cpu = self.cpu
if not self.isRawCall:
# Restore the AR2 register.
cpu.ar2.set(self.prevAR2value)
# Handle outbound parameters.
if self.block.isFB:
# We are returning from an FB.
# Get the multi-instance base offset.
instanceBaseOffset = AwlOffset.fromPointerValue(cpu.ar2.get())
# Restore the AR2 register.
cpu.ar2.set(self.prevAR2value)
# Transfer data out of DBI.
structInstance = cpu.diRegister.structInstance
for param in self.__outboundParams:
cpu.store(
param.rvalueOp,
structInstance.getFieldData(param.lValueStructField)
structInstance.getFieldData(param.lValueStructField,
instanceBaseOffset)
)
# Assign the DB/DI registers.
cpu.dbRegister, cpu.diRegister = self.instanceDB, self.prevDiRegister
else:
# We are returning from an FC.
# Restore the AR2 register.
cpu.ar2.set(self.prevAR2value)
# Transfer data out of temporary sections.
for param in self.__outboundParams:
cpu.store(
......
......@@ -238,7 +238,6 @@ class S7CPU(object): #+cdef
# Add interface references to the parameter assignments.
for param in insn.params:
param.interface = calledCodeBlock.interface
param.instanceDB = calledDataBlock
def __finalizeCodeBlocks(self):
for block in self.__allUserCodeBlocks():
......@@ -485,7 +484,7 @@ class S7CPU(object): #+cdef
self.ar1.reset()
self.ar2.reset()
self.statusWord.reset()
self.callStack = [ CallStackElem(self, block, None, (), True) ]
self.callStack = [ CallStackElem(self, block, None, None, (), True) ]
cse = self.callStackTop = self.callStack[-1]
if self.__obTempPresetsEnabled:
# Populate the TEMP region
......@@ -691,45 +690,41 @@ class S7CPU(object): #+cdef
def __call_FC(self, blockOper, dbOper, parameters):
fc = self.fcs[blockOper.value.byteOffset]
return CallStackElem(self, fc, None, parameters)
return CallStackElem(self, fc, None, None, parameters)
def __call_RAW_FC(self, blockOper, dbOper, parameters):
fc = self.fcs[blockOper.value.byteOffset]
return CallStackElem(self, fc, None, (), True)
return CallStackElem(self, fc, None, None, (), True)
def __call_FB(self, blockOper, dbOper, parameters):
fb = self.fbs[blockOper.value.byteOffset]
db = self.dbs[dbOper.value.byteOffset]
cse = CallStackElem(self, fb, db, parameters)
cse = CallStackElem(self, fb, db, AwlOffset(), parameters)
self.dbRegister, self.diRegister = self.diRegister, db
# Set AR2 to multi-instance base DBX 0.0
self.ar2.set(AwlIndirectOp.AREA_DB)
return cse
def __call_RAW_FB(self, blockOper, dbOper, parameters):
fb = self.fbs[blockOper.value.byteOffset]
return CallStackElem(self, fb, self.diRegister, (), True)
return CallStackElem(self, fb, self.diRegister, None, (), True)
def __call_SFC(self, blockOper, dbOper, parameters):
sfc = self.sfcs[blockOper.value.byteOffset]
return CallStackElem(self, sfc, None, parameters)
return CallStackElem(self, sfc, None, None, parameters)
def __call_RAW_SFC(self, blockOper, dbOper, parameters):
sfc = self.sfcs[blockOper.value.byteOffset]
return CallStackElem(self, sfc, None, (), True)
return CallStackElem(self, sfc, None, None, (), True)
def __call_SFB(self, blockOper, dbOper, parameters):
sfb = self.sfbs[blockOper.value.byteOffset]
db = self.dbs[dbOper.value.byteOffset]
cse = CallStackElem(self, sfb, db, parameters)
cse = CallStackElem(self, sfb, db, AwlOffset(), parameters)
self.dbRegister, self.diRegister = self.diRegister, db
# Set AR2 to multi-instance base DBX 0.0
self.ar2.set(AwlIndirectOp.AREA_DB)
return cse
def __call_RAW_SFB(self, blockOper, dbOper, parameters):
sfb = self.sfbs[blockOper.value.byteOffset]
return CallStackElem(self, sfb, self.diRegister, (), True)
return CallStackElem(self, sfb, self.diRegister, None, (), True)
def __call_INDIRECT(self, blockOper, dbOper, parameters):
blockOper = blockOper.resolve()
......@@ -741,12 +736,16 @@ class S7CPU(object): #+cdef
blockOper.value.byteOffset)
def __call_MULTI_FB(self, blockOper, dbOper, parameters):
pass#TODO
raise AwlSimError("Multi-instance calls not supported, yet")
fb = self.fbs[blockOper.value.fbNumber]
cse = CallStackElem(self, fb, self.diRegister, blockOper.value, parameters)
self.dbRegister = self.diRegister
return cse
def __call_MULTI_SFB(self, blockOper, dbOper, parameters):
pass#TODO
raise AwlSimError("Multi-instance calls not supported, yet")
sfb = self.sfbs[blockOper.value.fbNumber]
cse = CallStackElem(self, sfb, self.diRegister, blockOper.value, parameters)
self.dbRegister = self.diRegister
return cse
__callHelpers = {
AwlOperator.BLKREF_FC : __call_FC,
......
......@@ -237,11 +237,18 @@ class AwlStructInstance(object):
"initialization is out of range." %\
str(field))
def getFieldData(self, field):
return self.dataBytes.fetch(field.offset, field.bitSize)
def getFieldData(self, field, baseOffset=None):
if baseOffset is None:
return self.dataBytes.fetch(field.offset, field.bitSize)
return self.dataBytes.fetch(baseOffset + field.offset, field.bitSize)
def setFieldData(self, field, value):
self.dataBytes.store(field.offset, field.bitSize, value)
def setFieldData(self, field, value, baseOffset=None):
if baseOffset is None:
self.dataBytes.store(field.offset,
field.bitSize, value)
else:
self.dataBytes.store(baseOffset + field.offset,
field.bitSize, value)
def getFieldDataByName(self, name, arrayIndex=None):
return self.getFieldData(self.struct.getField(name, arrayIndex))
......
......@@ -7,5 +7,6 @@ cdef class AwlOffset(DynAttrs):
cdef public object dbNumber
cdef public object dbName
cdef public object varName
cdef public object fbNumber
cdef public object indices
cdef public object subOffset
......@@ -57,10 +57,15 @@ class AwlParamAssign(DynAttrs):
}
def __init__(self, lvalueName, rvalueOp):
# A parameter assignment consists of an lvalue and an rvalue:
# LVALUE := RVALUE
# 'lvalueName' is the name string of the lvalue.
# 'rvalueOp' is the AwlOperator that represents the rvalue.
self.lvalueName = lvalueName
self.rvalueOp = rvalueOp
# 'interface' is the BlockInterface of the called block.
# This element is assigned later in the translation phase.
self.interface = None
self.instanceDB = None
def __isInbound(self):
field = self.interface.getFieldByName(self.lvalueName)
......@@ -74,7 +79,7 @@ class AwlParamAssign(DynAttrs):
def __lValueStructField(self):
# Find the l-value struct field
return self.instanceDB.structInstance.struct.getField(self.lvalueName)
return self.interface.struct.getField(self.lvalueName)
def __interfaceFieldIndex(self):
# Find the index number for the l-value
......
DATA_BLOCK DB 1
FB 1
BEGIN
END_DATA_BLOCK
// FB 2, embedded as multi-instance
FUNCTION_BLOCK FB 2
VAR
TEST_STAT1 : DWORD;
END_VAR
VAR_INPUT
TEST_IN1 : DINT;
END_VAR
VAR_OUTPUT
TEST_OUT1 : WORD;
END_VAR
BEGIN
__ASSERT== __AR 2, P#DBX 2.0 // Check multi-instance base
// Check DB/DI registers
L DBNO
__ASSERT== __ACCU 1, 1
L DINO
__ASSERT== __ACCU 1, 1
// Get input, add 1, store in static.
L #TEST_IN1
L 1
+D
T #TEST_STAT1
// Get static, add 1, store in output.
L #TEST_STAT1
L 1
+D
T #TEST_OUT1
END_FUNCTION_BLOCK
// FB 1, called with instance-DB. Embeds multi-instances.
FUNCTION_BLOCK FB 1
VAR
GUARD0 : WORD; // Guard field
MULTI_FB2 : FB 2; // Embedded FB 2 multi-instance
GUARD1 : WORD; // Guard field
MULTI_SFB0 : SFB 0; // Embedded SFB 0 multi-instance
GUARD2 : WORD; // Guard field
END_VAR
VAR_TEMP
TEMP1 : WORD;
COUNT : INT;
END_VAR
BEGIN
__ASSERT== __AR 2, P#DBX 0.0 // Check multi-instance base
// Check DB/DI registers
L DBNO
__ASSERT== __ACCU 1, 0
L DINO
__ASSERT== __ACCU 1, 1
// Write the guard fields to detect errors in the
// multi instance base offset (AR2).
L W#16#4220 // Magic value
T #GUARD0
L W#16#4221 // Magic value
T #GUARD1
L W#16#4222 // Magic value
T #GUARD2
// Call the multi-instance FB 2
CALL #MULTI_FB2 (
TEST_IN1 := L#77,
TEST_OUT1 := #TEMP1,
)
L #TEMP1
__ASSERT== __ACCU 1, 79 // result = input + 2
// Call the multi-instance SFB 0
CALL #MULTI_SFB0 (
CU := FALSE,
CV := #COUNT,
)
CALL #MULTI_SFB0 (
CU := TRUE,
CV := #COUNT,
)
CALL #MULTI_SFB0 (
CU := FALSE,
CV := #COUNT,
)
L #COUNT
__ASSERT== __ACCU 1, 1
// Check all instance-DB values.
// Use fully qualified accesses to avoid implicit usage of AR2 base.
L DINO // DI = DB1
__ASSERT== __ACCU 1, 1
L DB1.DBW 0 // DB1.GUARD0
__ASSERT== __ACCU 1, W#16#4220
L DB1.DBD 2 // DB1.MULTI_FB2.TEST_IN1
__ASSERT== __ACCU 1, 77
L DB1.DBW 6 // DB1.MULTI_FB2.TEST_OUT1
__ASSERT== __ACCU 1, 79
L DB1.DBD 8 // DB1.MULTI_FB2.TEST_STAT1
__ASSERT== __ACCU 1, 78
L DB1.DBW 12 // DB1.GUARD1
__ASSERT== __ACCU 1, W#16#4221
L DB1.DBW 14 // DB1.MULTI_SFB0.CU/R
__ASSERT== __ACCU 1, W#16#0000
L DB1.DBW 16 // DB1.MULTI_SFB0.PV
__ASSERT== __ACCU 1, 0
L DB1.DBW 18 // DB1.MULTI_SFB0.Q
__ASSERT== __ACCU 1, W#16#0100
L DB1.DBW 20 // DB1.MULTI_SFB0.CV
__ASSERT== __ACCU 1, 1
L DB1.DBW 22 // DB1.MULTI_SFB0.CUO
__ASSERT== __ACCU 1, W#16#0000
L DB1.DBW 24 // DB1.GUARD2
__ASSERT== __ACCU 1, W#16#4222
END_FUNCTION_BLOCK
ORGANIZATION_BLOCK OB 1
BEGIN
CALL FB 1, DB 1
CALL SFC 46 // STOP CPU
END_ORGANIZATION_BLOCK
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment