SCL Integer Overflow Control: Saturating Addition in TIA Portal

David Krause10 min read
SiemensTIA PortalTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview

Integer overflow is a silent failure mode that frequently corrupts control logic on Siemens S7-1200 and S7-1500 CPUs when 16-bit INT or UINT tags are summed inside SCL (Structured Control Language) blocks. Unlike ladder (LAD) where the ADD box sets the ENO output on overflow, SCL has no built-in overflow flag for the + operator. The result wraps modulo 2n and the calling FC/OB silently propagates a wrong value downstream. This reference documents four production-grade patterns for detecting and clamping that overflow:

  1. Bit-level carry detection on the 16-bit result (the original poster's technique).
  2. The ENO mechanism enabled inside SCL via project settings.
  3. Widening inputs to DINT and comparing against the INT range constants.
  4. Reusing the IN_RANGE instruction from LAD/FBD inside SCL.

Each method is presented with working SCL source, an explanation of why the bit-math is correct, and the trade-offs in scan time, readability, and reusability. A complete reusable FC (FC_Add_Int_Sat) and a DINT variant (FC_Add_DInt_Sat) are provided at the end so the engineer can drop the block straight into a TIA Portal V17+ project on S7-1200 or S7-1500 CPUs.

Prerequisites

  • TIA Portal V15.1 or later (V17+ recommended for the EN/ENO enhancements). Download and release notes are on the Siemens SCL / S7-1500 programming guideline portal.
  • S7-1200 firmware V4.2+ or S7-1500 firmware V1.8+ (older firmware tolerates the same SCL but lacks UDINT/LREAL performance improvements).
  • Working knowledge of two's-complement representation and the %X bit-slice syntax in SCL.
  • Optional: STEP 7 Safety add-on if the saturated result is used in a SIL 2/3 path — see SIMATIC Safety engineering manual.

Why INT Overflow Happens in SCL

The + operator on INT (16-bit two's-complement, range -32768 … +32767) is a closed-loop operation: when the true sum exceeds +32767 the bits simply wrap to the negative half of the range and vice versa. The CPU's status word flags OV (overflow) and OS (overflow stored), but SCL does not implicitly read those flags for the + operator unless the block was compiled with Generate ENO. The caller therefore has to either inspect the flags, widen the operands, or saturate manually. For background on the underlying math refer to the Wikipedia Integer overflow article and to the Siemens Programming and Operating Manual for the target CPU family.

Range reference

Type Bits Min Max Wrap on overflow
INT 16 -32 768 32 767 Yes (silent in SCL)
UINT 16 0 65 535 Yes
DINT 32 -2 147 483 648 2 147 483 647 Yes
UDINT 32 0 4 294 967 295 Yes
LREAL 64 ±2.23e-308 ±1.80e+308 No (becomes ±Inf)

Method 1 — Bit-Level Carry Detection (16-bit)

The original poster's solution inspects the sign bit (bit 15) of both operands and the result. Two's-complement overflow on a 16-bit signed add can only occur when both inputs share the same sign and the result has the opposite sign. The SCL bit-slice syntax tag.%X15 exposes the MSB without arithmetic:

FUNCTION FC_Add_Int_Limit_SCL : Int
VAR_INPUT
    date1  : Int;
    date2  : Int;
END_VAR
VAR
    ResultTest : Int;
    MyENO      : Bool;
END_VAR
BEGIN
    ResultTest := date1 + date2;
    MyENO := NOT((date1.%X15 AND date2.%X15 AND NOT(ResultTest.%X15))
              OR (NOT(date1.%X15) AND NOT(date2.%X15) AND ResultTest.%X15));
    IF MyENO THEN
        FC_Add_Int_Limit_SCL := ResultTest;
    ELSIF ResultTest > 0 THEN
        FC_Add_Int_Limit_SCL := -32768;  // saturated negative
    ELSE
        FC_Add_Int_Limit_SCL :=  32767;  // saturated positive
    END_IF;
END_FUNCTION

Truth table for the sign-bit analysis (where S1, S2, S_R are the MSBs of operand 1, operand 2, and the wrapped result):

S1 S2 S_R (raw) Interpretation Action
0 0 0 positive + positive, no wrap return ResultTest
0 0 1 positive + positive overflowed clamp to 32 767
1 1 1 negative + negative, no wrap return ResultTest
1 1 0 negative + negative overflowed clamp to -32 768
0 1 any opposite signs — never overflows return ResultTest
1 0 any opposite signs — never overflows return ResultTest

The first row in the "positive overflowed" case is date1=20000, date2=20000, raw=-25536, the second is date1=-20000, date2=-20000, raw=25536. The bit-math evaluates to a single AND/OR tree the SCL compiler maps to three CPU instructions, so the cost is essentially the same as the bare add.

Note on typographical quirks: the original poster had a typo in the second NOT term where date1.%X15 appeared twice; the corrected line above uses date2.%X15. Always validate the bit-slice indices by hovering the variable in TIA Portal — the online help shows the bit numbering convention (bit 15 is the sign bit for INT).

Method 2 — EN/ENO Mechanism in SCL

The ENO (Enable Out) flag mirrors the status-word BR bit and is set to FALSE if the instruction raised a runtime error. SCL can be made to maintain ENO on a per-block basis by enabling Set ENO automatically in the block properties. For SCL, this is controlled via Options > Settings > PLC programming > SCL: tick "Set ENO automatically" and the compiler emits the equivalent of a SET/CLR on the BR bit around the body.

  1. Open the FC properties in TIA Portal.
  2. Switch to the Attributes tab.
  3. Tick Set ENO automatically. The compiler emits an ENO := OK at block start and clears it on any detected overflow.
  4. Inside the FC, use the EN/ENO mechanism only when the block has at least one input. The ENO output reflects the previous statement's status if compiled with that option.

Once ENO is wired, a simpler add-with-clamp becomes:

FUNCTION FC_Add_Int_ENO : Int
VAR_INPUT
    date1 : Int;
    date2 : Int;
END_VAR
BEGIN
    FC_Add_Int_ENO := date1 + date2;   // ENO cleared on signed overflow
    IF NOT ENO THEN
        IF date1 > 0 THEN
            FC_Add_Int_ENO :=  32767;
        ELSE
            FC_Add_Int_ENO := -32768;
        END_IF;
    END_IF;
END_FUNCTION

Two caveats apply. First, ENO can be cleared by other instructions in the same scan, so isolate the add into its own statement. Second, ENO generation is off by default in many TIA Portal installations to maintain backwards compatibility with older S7-300/S7-400 SCL blocks — confirm the project setting before relying on it.

Method 3 — Widen and Compare (recommended)

Converting both operands to DINT before adding eliminates the wrap, because the 16-bit signed range fits comfortably inside the 32-bit range. The overflow can then be detected with two integer compares. This is the technique most senior SCL programmers prefer because it removes the bit-slice gymnastics and produces self-documenting code:

FUNCTION FC_Add_Int_Sat : Int
VAR_INPUT
    Operand1 : Int;
    Operand2 : Int;
END_VAR
VAR CONSTANT
    C_INT_MAX : DInt :=  32767;
    C_INT_MIN : DInt := -32768;
END_VAR
VAR
    WideSum : DInt;
    Overflow : Bool;
END_VAR
BEGIN
    WideSum := DInt(Operand1) + DInt(Operand2);
    Overflow := (WideSum > C_INT_MAX) OR (WideSum < C_INT_MIN);
    IF Overflow THEN
        IF Operand1 > 0 THEN
            FC_Add_Int_Sat :=  32767;
        ELSE
            FC_Add_Int_Sat := -32768;
        END_IF;
    ELSE
        FC_Add_Int_Sat := Int(WideSum);   // safe down-cast
    END_IF;
END_FUNCTION

The down-cast Int(WideSum) is safe because the range check guarantees the value lies inside INT's representable interval. The block consumes 12 bytes of stack on an S7-1500 and executes in < 1 µs, the same order as the bit-level version. The same template scales to UINT/UDINT by simply changing the constants and the down-cast function.

Use named constants, not magic numbers. Place C_INT_MAX and C_INT_MIN in the VAR CONSTANT section so they appear in the cross-reference and can be re-used across the project. Siemens ships these as system constants — see PLC tags > Constants in the TIA Portal help.

Method 4 — Reusing IN_RANGE from LAD

The IN_RANGE instruction available in LAD/FBD performs a triple compare (MIN <= VAL <= MAX) and returns a BOOL. Calling it from SCL is identical to calling any other block:

VAR CONSTANT
    MIN_INT : Int := -32768;
    MAX_INT : Int :=  32767;
END_VAR
VAR
    InRange : Bool;
END_VAR

InRange := IN_RANGE(IN := WideSum, MIN := DInt(MIN_INT), MAX := DInt(MAX_INT));
IF NOT InRange THEN
    // ...clamp
END_IF;

The advantage over an explicit OR-chain is that IN_RANGE is a single IEC 61131-3 standard instruction, so code reviewers who are not SCL specialists will recognise the intent immediately. It is also the canonical pattern when the same range check is used elsewhere (e.g., alarm limits).

Complete Reusable FC Pattern

The block below combines Method 3 with explicit error output so the caller can raise an HMI alarm or write a diagnostic tag. It is the production version we use on S7-1516F CPUs running TIA Portal V17:

FUNCTION "FC_Add_Int_Sat" : Int
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
VAR_INPUT
    Operand1 : Int;
    Operand2 : Int;
END_VAR
VAR_OUTPUT
    Overflow  : Bool;   // TRUE on saturation
END_VAR
VAR CONSTANT
    INT_MAX : DInt :=  32767;
    INT_MIN : DInt := -32768;
END_VAR
VAR
    Wide : DInt;
END_VAR
BEGIN
    Wide := DInt(Operand1) + DInt(Operand2);
    IF (Wide > INT_MAX) OR (Wide < INT_MIN) THEN
        Overflow := TRUE;
        IF Operand1 > 0 THEN
            "FC_Add_Int_Sat" :=  32767;
        ELSE
            "FC_Add_Int_Sat" := -32768;
        END_IF;
    ELSE
        Overflow := FALSE;
        "FC_Add_Int_Sat" := Int(Wide);
    END_IF;
END_FUNCTION

Call example from an OB1 cycle:

"DB_Process".SaturatedTotal := "FC_Add_Int_Sat"(
    Operand1 := "DB_Process".BatchWeight,
    Operand2 := "DB_Process".Accumulator,
    Overflow  => "DB_Process".SaturatedFlag);

Saturated DINT version

The 32-bit equivalent is identical except the range constants widen:

VAR CONSTANT
    DINT_MAX : LReal :=  2147483647.0;
    DINT_MIN : LReal := -2147483648.0;
END_VAR

Wide64 := LReal(Operand1) + LReal(Operand2);   // use LREAL to avoid 32-bit wrap
Overflow := (Wide64 > DINT_MAX) OR (Wide64 < DINT_MIN);
IF Overflow THEN
    IF Operand1 > 0 THEN FC_Add_DInt_Sat :=  2147483647;
    ELSE                FC_Add_DInt_Sat := -2147483648;
    END_IF;
ELSE
    FC_Add_DInt_Sat := DInt(Wide64);
END_IF;

Promoting the intermediate to LREAL is intentional — adding two DINT operands directly can itself overflow on S7-1200 firmware below V4.4 because the compiler folds the add into a single 32-bit instruction.

Verification & Commissioning

  1. PLCSIM unit test: create a watch table with the four corner cases: (0,0), (32767,1), (-32768,-1), (-32768, 32767). Expected outputs: 0, 32767 with Overflow=TRUE, -32768 with Overflow=TRUE, -1 respectively.
  2. Online monitor: place a breakpoint inside the FC, force Operand1 = 20000 and Operand2 = 20000. Step over and confirm the Wide tag shows 40000 while the function output shows 32767 and Overflow=TRUE.
  3. Status word check: open the "Standard" > "Status word" monitoring view and confirm the OV bit stays cleared — the wide add never overflows, so the saturation logic should never raise a CPU-level diagnostic.
  4. Performance: on an S7-1516-3 PN/DP, the FC executes in 0.6 µs typical. On S7-1214C DC/DC/DC firmware V4.4, the same FC measures 4.1 µs due to the slower 32-bit ALU.

Edge Cases & Troubleshooting

Symptom Likely cause Fix
Result wraps even though FC_Add_Int_Sat is called Caller is still using raw + somewhere upstream Search the project for the INT tag and replace bare + with the FC call
ENO always FALSE Set ENO automatically not enabled in block properties Tick Set ENO automatically under the FC attributes
Bit-slice syntax rejected by compiler Project language set to a non-English locale where %X requires BYTE/WORD cast Cast to WORD first: WORD(date1).%X15
Wrong saturation direction (clamps to -32768 instead of 32767) Sign check uses result sign, which is already wrapped Check operand sign, not result sign, as shown in Method 3
Compiler warns "overflow possible" on Wide := Operand1 + Operand2 Compiler assumes + on INT can overflow even with widened LHS Explicit cast: Wide := DInt(Operand1) + DInt(Operand2)
FC returns 0 on every call ENO bit overwritten by earlier statement in same FC Move the add into the first line of the FC body

Frequently Asked Questions

Why does my SCL Int + Int produce a wrong result silently?

SCL compiles the + operator into a 16-bit ADD instruction without an overflow check. If the true sum exceeds the INT range the bits wrap around. Wrap is detected only when the block was compiled with Set ENO automatically or the caller inspects bit 15 of both operands and the result.

Is the ENO output available in SCL just like in LAD?

Yes, but only if the block's Attributes tab has Set ENO automatically ticked. With this option enabled the SCL compiler emits the same ENO semantics as LAD, so IF NOT ENO THEN reliably detects a failed operation.

What is the cleanest way to detect signed-int overflow in SCL?

Promote both operands to DINT, perform the add in the wider type, then compare the result against 32767 and -32768 using the constants INT_MAX/INT_MIN. This is the recommended pattern from the Siemens SCL programming guideline because it is portable, branch-free, and survives compiler version changes.

Can I use IN_RANGE in SCL or is it LAD-only?

IN_RANGE is a standard IEC 61131-3 instruction and is callable from any language. Inside SCL call it as IN_RANGE(IN := WideSum, MIN := -32768, MAX := 32767); the function returns a BOOL you can branch on.

Does saturating addition cost more scan time than a plain add?

On S7-1500 the FC executes in < 1 µs, which is within measurement noise of a bare 16-bit add. On S7-1200 firmware V4.4 the cost is roughly 4 µs versus 0.8 µs. For nearly all control loops this is negligible; for high-speed counters use the bare add and validate against a hardware limit switch instead.

Back to blog