Why STL L and T Are Unconditional
Statement List (STL) on the SIMATIC S7-300 and S7-400 treats the L (Load) and T (Transfer) instructions as pure accumulator operations. Unlike a Ladder Diagram (LAD) MOVE box, they do not condition on the Result of Logic Operation (RLO). The naive snippet below compiles but executes the transfer every scan regardless of I 0.0:
A I 0.0
L 20
T QB10
The misconception here is that the A instruction qualifies the subsequent L. It does not. A I 0.0 only writes bit 1 (RLO) of the Status Word; L then pushes the constant 20 into ACCU1, and T QB10 always writes ACCU1's low byte to output byte 10. The fix is to interpose a conditional construct (a jump, a A( ) block, or a MOVE-equivalent from LAD/FBD) that consults the RLO before doing the transfer.
JC/JCN, branch into separate code blocks, or substitute the L/T pair with an instruction whose enable input is the RLO (such as a MOVE block generated in LAD/FBD, or a single-purpose FC).The S7-300/400 Status Word Foundation
Every binary and math instruction on the S7-300/400 CPU updates a 16-bit register called the Status Word (STW). Understanding which bit is set when is mandatory before implementing conditional STL patterns. The relevant bits are:
| Bit | Mnemonic | Name | Set/Cleared By | Read By |
|---|---|---|---|---|
| 0 | /FC | First Check | First bit-test in a logic string | — (internal) |
| 1 | RLO | Result of Logic Operation | A, AN, O, ON, X, XN, NOT, SET, CLR, comparisons, math, shift, rotate, conversion | JC, JCN, JCB, JNBI, JL, JO, JOS, =, S, R, BEC, CC/UC |
| 2 | /STA | Status | Bit instructions referencing memory | — (informational) |
| 3 | /OR | OR Requirement Bit | O/ON chains (pre-AND before OR) | — (informational) |
| 4 | OV | Overflow | Math, float, conversion, compare | JO, JOS |
| 5 | OS | Stored Overflow | OV (latched until next status-updating instruction) | JOS |
| 6 | CC0 | Condition Code 0 | Compare, math, shift, rotate, conversion, word logic | JP, JM, JMZ, JPZ, JUO, JL via CC0/CC1 |
| 7 | CC1 | Condition Code 1 | Same as CC0 | Same as CC0 |
| 8 | BR | Binary Result | SAVE, FB ENO pin, write instructions, FC/FB ENO | JC on a CALL FB (CC, UC, BEC), JNBI, A BR, AN BR |
Source: Siemens SIMATIC S7-300/400 STL Programming Manual – Status Word section. The RLO (bit 1) is the bit that controls JC (jump if RLO = 1), JCN (jump if RLO = 0), and the write-enable of =, S, R. BR (bit 8) is not loadable: there is no L BR instruction in S7-300/400 STL. You can only save the current RLO into BR with the SAVE instruction, or read BR via A BR / AN BR and via the ENO of a called FB.
Pattern 1 — Conditional Transfer with Jump Guards
To make a transfer behave like a Ladder MOVE gated on I 0.0, do not try to condition the T itself. Instead, skip the transfer when the guard is false. Two compact idioms work.
Idiom A — JC over a single transfer with explicit else
JC _DO // RLO already set by a prior A/AN
L 0
T QB10
JU _END
_DO: L 20
T QB10
_END: NOP 0
The CPU executes the else-path (write 0) when the input is false, and the then-path (write 20) when true. The trailing JU _END is critical — without it, both transfers would execute on the truthy path.
Idiom B — Test guard then jump to skip transfer
AN I 0.0 // RLO = 1 when I0.0 == 0
JC _SKIP
L 20
T QB10
_SKIP: NOP 0
Here AN inverts the guard, so JC jumps over the assignment when I 0.0 is 0. Both forms compile to the same stack depth; Idiom B is the cleanest for single conditional writes.
JC costs ~1 µs of execution time, an L/T pair costs ~1–2 µs. Conditional transfers in STL are typically cheaper than the equivalent LAD MOVE block, which is interpreted through the LAD/FBD compiler's intermediate form.Idiom C — Conditional Call
If the transfer lives inside an FC, you can call it conditionally with CC FCn instead of CALL FCn. CC consults the current RLO and, if it is 1, performs the call. UC FCn is unconditional and ignores the RLO entirely:
A I 0.0
CC FC 1 // executes FC1 only when I0.0 == 1
L 0
T QB10
Pattern 2 — AND-Chained Comparisons (==I, ==R, ==D)
A common attempt at multi-condition logic looks like this:
A "E1_3"
L "sMM1"
L "SPsMM1"
==I
L "sMM2"
L "SPsMM1"
==I
JC _001
Why does this fail? Because ==I is a comparison, not a pure RLO-write. Each ==I replaces the RLO based on the result of the comparison (equal, not-equal, greater, less, etc.), and the accumulator is unaffected after the comparison is evaluated. So the second ==I overwrites the RLO from the first. The fix is to AND the comparison results into a single RLO explicitly, or to use the A( ... ) parenthesis construct.
Solution A — Use A( ... ) parentheses
A(
L "sMM1"
L "SPsMM1"
==I
)
A(
L "sMM2"
L "SPsMM1"
==I
)
JC _001
Inside each A( ... ) block, the comparison's RLO is saved onto the OR stack (/OR), the parenthesis closes, and the A ahead of the next open-paren ANDs the saved RLO with the new one. After the second ), the final RLO is 1 only if both comparisons were equal.
Solution B — Manual AND using A ==I
STL allows the shortcut where the comparison is treated as a single bit-test instruction: A ==I directly writes the RLO from the comparison result and ANDs it with the previous RLO. The two L operands remain in ACCU1/ACCU2 for re-use across the next ==I:
A "E1_3" // optional precondition
A ==I // sMM1 == SPsMM1 ?
A ==I // sMM2 == SPsMM1 ?
JC _001
The first ==I needs the two operands loaded with L ahead of the chain. The second ==I consumes the operands from ACCU1/ACCU2 (which were left there by the previous ==I). The explicit A prefix on the second ==I ANDs the new RLO with the previous one.
Solution C — Save RLO to BR and re-AND with A BR
A "E1_3"
L "sMM1"
L "SPsMM1"
==I
SAVE // RLO -> BR
A BR // re-AND with the current RLO state
L "sMM2"
L "SPsMM1"
==I
JC _001
Important caveats:
-
A BRchecks status word bit 8. On S7-300/400 this is a real addressable bit, so the pattern works. - On S7-1500 the Status Word layout is extended, but
A BRis preserved as a compatibility instruction. Newer compilers prefer theA( ... )pattern. - There is no
L BRon either platform. The question's author asked the right thing; the answer is: you cannot load BR into ACCU1, you can only test it withA BR/AN BRor read it via the ENO of a called FB. - The
SAVEinstruction is destructive across nested calls — if you SAVE before calling an FB that itself returns an ENO, the BR is overwritten by that ENO. Use theJCB/JNBIpair (jump if RLO=1 with save, jump if BR=0) when you need to save and test BR across a call boundary.
Comparison Result → RLO and CC-Bit Mapping
For reference, the CC1/CC0 pair maps to the four possible integer comparison results. The S7-300/400 mapping is:
| CC1 | CC0 | Integer Compare | Float Compare | Jump If | Mnemonic |
|---|---|---|---|---|---|
| 0 | 0 | == 0 / == FALSE | == 0.0 / == FALSE | RLO = 1, ACCU1-L = 0 | JZ / JPZ |
| 0 | 1 | < 0 / < | < 0.0 / < | JM | less than |
| 1 | 0 | > 0 / > | > 0.0 / > | JP | greater than |
| 1 | 1 | UO (unordered, NaN) | UO (unordered, NaN) | JUO | invalid compare (only on real types) |
For unsigned 16-bit, < and > are interpreted modulo 65536; use <D/>D (double integer) when you need a clean 0..32767 range.
Network Limits and CALL Instruction Scaling
There is no hard S7-300/400 CPU limit on the total number of CALL FCx instructions in OB1, but there is a network length limit: a single STL network is capped at 999 lines in the Step 7 editor. Within that, each CALL compiles to a BStack (Block Stack) push/pop of the return address plus a fixed call-setup cost. Practical limits observed in the field:
| CPU | BStack Depth (max nesting) | LStack per Priority Class | Re-entrancy |
|---|---|---|---|
| CPU 312 | 8 | 256 bytes | No |
| CPU 313 / 314 | 8 | 256 bytes | No |
| CPU 315-2 DP / 315-2 PN/DP | 16 | 1024 bytes | Yes (limited) |
| CPU 317-2 DP / 317-2 PN/DP | 32 | 1024 bytes | Yes |
| CPU 319-3 PN/DP | 32 | 2048 bytes | Yes |
| CPU 412-1 / 412-2 | 24 | 4096 bytes | Yes (full) |
| CPU 414-2 / 414-3 | 32 | 4096 bytes | Yes (full) |
| CPU 416-2 / 416-3 | 64 | 4096 bytes | Yes (full) |
If you write 50 sequential CALL FCn instructions back-to-back in OB1, you will not run out of BStack — the calls are stacked and unstacked sequentially, so the maximum depth at any instant is 1. What you will consume is scan time: each CALL takes ~5–10 µs of overhead (save RLO/STA, push return address, jump to FC, restore, return) plus the FC's own body. The "huge delay" that one engineer worried about does not exist for sequential unconditional calls; the delay is purely the cumulative body time of the 50 FCs.
CALL FCn inside an A I 0.0 / JC block is still unconditional with respect to the RLO. Use CC FCn to gate the call on the current RLO, and UC FCn to call unconditionally. CALL FCn is the legacy unconditional form retained for compatibility.S7-200 Special Memory (SMB) vs S7-300 System Resources
The S7-200 family ships with a rich Special Memory Byte (SMB) area that the S7-300/400 does not provide by default. The mapping for engineers porting S7-200 logic to S7-300 is:
| SMB Range | Function on S7-200 | S7-300/400 Equivalent |
|---|---|---|
| SM0.0 | Always_On (1 every scan) | Implement via SET; = M 100.0 in OB1, or any FC |
| SM0.1 | First_Scan_On (1 on first scan only) | Set a flag inside OB100 (warm restart) or OB101/OB102 (hot/cold restart) |
| SM0.4 / SM0.5 | 1-min / 1-sec clock toggling | Enable "Clock memory" in CPU hardware configuration and assign a marker byte (e.g. MB10) |
| SM0.6 / SM0.7 | Scan-clock (2 × scan-time toggling) | Use OB1_PREV_CYCLE TEMP for last-scan time, plus a clock memory byte |
| SMB1–SMB9 | Error and instruction-status bits | Diagnostic buffer + status bits in OB1 TEMP / OB100 TEMP |
| SMB28–SMB29 | Analog-potentiometer raw values | No equivalent on most S7-300 CPUs; S7-1500 reads analog inputs directly as tags |
| SMW22 / SMW24 | Min / max scan time (ms) |
OB1_MIN_CYCLE and OB1_MAX_CYCLE in OB1 TEMP |
| SMB34 / SMB35 | Timed-interrupt interval set | Configure OB35 period in hardware config (Cyclic Interrupt, default 100 ms) |
For an Always_On flag on the S7-300:
SET // forces RLO = 1
= M 100.0 // "always on" marker bit
For a First_Scan_On flag, set it in OB100 (or OB101/OB102 depending on restart type) and clear it in OB1 after the first scan:
// In OB100 (warm restart):
SET
= "FirstScanDone"
// In OB1, at the end of the cycle:
AN "FirstScanDone"
JC _SKIP
// ... first-scan logic ...
SET
= "FirstScanDone"
_SKIP: NOP 0
For clock bits (10 Hz, 5 Hz, 2.5 Hz, 2 Hz, 1.25 Hz, 1 Hz, 0.625 Hz, 0.5 Hz), open the CPU's properties in HW Config (Step 7) or device configuration (TIA Portal), tick "Clock memory", and assign a marker byte (commonly MB10 through MB255, excluding bytes used by the process).
OB1 TEMP: Scan-Time Variables for Free
The S7-300/400 OB1 block exposes a set of TEMP variables that the CPU updates every cycle. The most useful for diagnostics and trending:
| TEMP | Type | Meaning |
|---|---|---|
| OB1_EV_CLASS | BYTE | Event class (B#16#11 = OB1 standard scan) |
| OB1_SCAN_1 | TIME | Scan time of the previous OB1 pass (ms resolution as TIME) |
| OB1_PRIORITY | BYTE | Priority class (default 1 for OB1) |
| OB1_OB_NUMBR | BYTE | OB number (1) |
| OB1_RESERVED_1 | BYTE | Reserved |
| OB1_RESERVED_2 | BYTE | Reserved |
| OB1_PREV_CYCLE | TIME | Previous-cycle execution time (ms, as TIME) |
| OB1_MIN_CYCLE | TIME | Minimum cycle time since last cold restart |
| OB1_MAX_CYCLE | TIME | Maximum cycle time since last cold restart |
| OB1_DATE_TIME | DATE_AND_TIME | Date/time OB1 was called (BCD-encoded) |
These are read-only from your program. To log them to a DB, move them at the top of OB1:
L #OB1_PREV_CYCLE
T "Diag".LastCycle
L #OB1_MAX_CYCLE
T "Diag".MaxCycle
L #OB1_MIN_CYCLE
T "Diag".MinCycle
L #OB1_DATE_TIME
T "Diag".StampDTL
For a full list of OB1 TEMP variables, refer to the Siemens SIMATIC S7-300/400 OB block reference.
FB vs Direct Code: Performance and Memory Trade-off
A field case that is regularly reported on S5/115U and older S7-300 systems: an engineer wraps every conditional move in an FB call to "act like a one-shot" or "make a conditional transfer". Inlining those FBs into direct flag/network code produces a dramatic scan-time improvement. Two causes drive this:
-
FB call overhead. Every
CALL FB1consumes 6–12 µs of marshalling time (parameter copy from instance DB to LStack, BStack push/pop, DI register setup, return). For a one-line "conditional transfer" that is 90 % overhead and 10 % useful work. -
Instance DB load. The FB's
IN/OUT/STAT/TEMPvariables are read from the instance DB on entry, copied to LStack, and (for OUT) written back on exit. A "small" FB with 2 IN / 1 OUT still marshals three values per call.
Modern S7-300 CPUs (315-2 PN/DP, 317, 319) and all S7-1500 CPUs have partially closed the gap via optimised FB calls (TIA Portal's "Optimised block access" and the multi-instance DB compiler pass can inline single-instance FBs), but the principle still holds: do not wrap a single STL instruction in an FB for "code clarity". If you need a one-shot, write the two-network pattern using the FP edge detection instruction directly:
A "RunCmd"
FP "RunEdge" // FP uses RunEdge's own bit as edge memory
JCN NOOP
// rising-edge body here
NOOP: NOP 0
The FP instruction handles the edge memory bit for you; no FB needed. Reserve FBs for reusable, parameterised code (PID blocks, valve sequencers, recipe state machines), not for one-line conditional moves.
Verification: A Worked Example
Combine the patterns into a complete STL block that runs an axis only when three sensor inputs agree, sets a command word only on the rising edge, and logs the previous scan time to a DB.
// Network 1: three-input AND
A "E1_3"
A(
L "sMM1"
L "SPsMM1"
==I
)
A(
L "sMM2"
L "SPsMM1"
==I
)
= "AllEqual"
// Network 2: rising-edge one-shot on AllEqual
A "AllEqual"
FP "AllEqualEdge"
= "StartCmd"
// Network 3: conditional transfer — only write on the edge
A "AllEqualEdge"
JCN NOOP
L 20
T "AxisSpeed"
NOOP: NOP 0
// Network 4: log scan time
L #OB1_PREV_CYCLE
T "Diag".LastCycleMs
L #OB1_MAX_CYCLE
T "Diag".MaxCycleMs
Walk-through of what the CPU does each scan:
- Network 1 sets
AllEqualonly whenE1_3 == 1and the two INT compares both succeeded. TheA( )blocks save each comparison RLO onto the OR stack and AND it with the next. - Network 2 captures the rising edge of
AllEqualintoAllEqualEdgeusing theFPinstruction's internal edge memory bit. - Network 3 uses Idiom B:
JCN NOOPskips the transfer unless the edge fired. No FB, no MOVE block, no race window. - Network 4 reads the
OB1_PREV_CYCLETEMP into a DB for trending in WinCC.
Verify in STEP 7 / TIA Portal by setting breakpoints (or, in TIA Portal, using "Monitor/Modify" on the STW) and confirming:
- RLO is 1 at the
JCN NOOPlocation whenAllEqualEdge == 1. - CC1/CC0 are both 1 (equal) immediately after each
==I. - The OR stack (visible in STL online monitor) collapses correctly after each closing
). -
OB1_PREV_CYCLEreports a stable value within the configured max cycle time.
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
| Transfer executes every scan, ignoring input | Used A I x.y before L/T expecting gating |
Use JCN skip pattern (Idiom B), JC branch pattern (Idiom A), or A( block with conditional write |
| JC fires on the wrong condition | Chained comparisons without A( ) or A ==I prefix |
Add A prefix to each comparison, or wrap in A( )
|
Compiler rejects L BR
|
BR is a status-word bit, not an accumulator-loadable | Use SAVE to write RLO to BR; read BR with A BR or via FB ENO |
| SF (System Fault) after CALL chain | BStack overflow from recursive CALL | Audit CALL graph; ensure no FC/FB calls itself directly or via mutual recursion |
| First-scan logic runs every scan | Set FirstScan in OB1 instead of OB100 |
Move the SET+= from OB1 to OB100; clear FirstScan in OB1 if needed |
| Scan time spiked 5–10 ms after a refactor | Inline logic replaced with multi-instance FB calls | Inline small logic into direct networks; reserve FB for reusable, parameterised code |
| Clock bits (M10.0, M10.1, …) all 0 | Clock memory not enabled in hardware configuration | Open CPU properties → "Clock memory" → tick and assign a marker byte |
| Compiler reports "Cannot load status word bit" | Tried L BR, L RLO, L OV
|
Use A BR, JC, JO to test bits; do not attempt to load them into an accumulator |
| OB1_MIN_CYCLE / OB1_MAX_CYCLE always 0 | Read before the first cold restart has elapsed | Cycle counters reset on cold restart; wait one full warm restart or read after power-on |
SAVE inside an FC followed by CC FB returns wrong ENO |
SAVE consumed by the FB's ENO write | Move the SAVE after the FB call, or use JCB / JNBI to preserve the saved RLO across the call |
How do I make a conditional Move in STL on S7-300/400?
Use the skip pattern: AN I 0.0 followed by JC _SKIP to bypass the L/T pair when the guard is false, or JC _DO to jump to the L/T pair when the guard is true. L and T themselves never condition on the RLO, so you must guard the transfer with a jump or an A( ) block.
Why does my second ==I overwrite the first comparison's result?
Each ==I replaces the RLO with the comparison result. To AND two compares, wrap them in A( ... ) blocks, or prefix the second ==I with A (e.g. A ==I) so the CPU ANDs the new result with the previous RLO.
Can I load the BR bit into ACCU1 with L BR?
No. BR is status-word bit 8 and is not loadable on S7-300/400 (the same rule applies to S7-1500). Use SAVE to write the current RLO into BR, and A BR / AN BR to test it. You can also read BR via the ENO of a called FB.
How do I get an Always_On or First_Scan_On bit on S7-300?
Set a flag in OB1 with SET; = M 100.0 for Always_On. For First_Scan_On, set a flag inside OB100 (warm restart) and clear it after the first execution in OB1; do not place first-scan logic in OB1 itself, because OB1 runs every cycle.
Is there a limit to how many CALL FC instructions I can chain in OB1?
No hard count, but a single STL network is capped at 999 lines and the BStack depth (8–64 depending on CPU) limits simultaneous nesting. Sequential unconditional calls only ever use depth 1, so 50 calls back-to-back are safe — the scan-time cost is the sum of the FCs' execution times plus roughly 5–10 µs call overhead each.
Why did my scan time drop dramatically when I inlined a small FB into a direct network?
FB calls add 6–12 µs of parameter-marshalling and BStack overhead per invocation. For a one-line conditional move, that overhead dwarfs the useful work. Inline trivial logic into direct networks and reserve FBs for reusable, parameterised code (PID blocks, sequencers, recipe state machines). Modern S7-300/400 firmware plus TIA Portal's "Optimised block access" partially closes the gap, but the rule of thumb still holds for S5-ported code on legacy CPUs.