Siemens STEP 7 Bit Search: Cross-Reference and Addressing Modes

David Krause18 min read
S7-300SiemensTechnical Reference
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

Siemens STEP 7 Bit Search: Cross-Reference and Addressing Modes

Troubleshooting an S7-300/S7-400 program written in STEP 7 V5.x is rarely a problem of "where is the bit?" and almost always a problem of "which address mode did the original programmer use, and does the toolchain expose that mode to me at all?" The Cross-Reference list, the Go To Location shortcut, and the block-source generator each have specific rules about what they will and will not report. This reference consolidates the four addressing taxonomies (absolute vs. symbolic, direct vs. indirect, plus the indexed variant available in SCL), maps each one to a typical STEP 7 search workflow, and gives you the STL/SCL syntax, register usage, and verification steps required to trace any bit, byte, word, or double word across a project.

1. The Four-Term Addressing Taxonomy in STEP 7

STEP 7 documentation separates address notation from address resolution. Confusing the two axes is the most common reason a service engineer "loses" a tag. Treat the terms as a 2x2 grid plus one extra column:

Term What it describes Example Visible in Cross-Reference?
Absolute Memory location written in Siemens syntax IW200, M23.2, DB49.DBD16 Yes (always)
Symbolic User-assigned alias linked to an absolute "WaterLevel", "DirnIndicator", "AV1037" Yes when the symbol table is loaded with the program
Direct Address fixed at edit time A I 0.0 Yes
Indirect Address resolved at runtime via pointer A I [AR1, P#0.0] NO - resolved at runtime, never displayed
Indexed (SCL) Array element selected by a runtime index PCode := Location[Aisle, Column, Shelf]; Only the array base; the index expression is not

Rule of thumb: if a piece of data is reachable through the Cross-Reference list, it is direct (absolute or symbolic). If a piece of data is not reachable, it is either indirect, indexed, or it lives inside a UDT/instance-DB that was generated from a shared DB and addressed with a manipulated address register.

2. Absolute Addressing - Syntax Reference

STEP 7 absolute addresses always encode area + size + offset. The size token is the only thing that varies between an input word, a flag bit, and a data-block double word.

Area token Size token Full address Meaning
I / IB / IW / ID bit / byte / word / DWord I 0.0 ... ID 200 Process-input image (bit/byte/word/double word)
Q / QB / QW / QD same Q 12.0 ... QD 50 Process-output image
M / MB / MW / MD same M 23.2, MD 100 Merker (flag) memory, retained if configured
DB / DI / DBX / DBB / DBW / DBD same DB49.DBX 22.4, DB49.DBD 16 Data block (global / instance)
T / C n/a T 5, C 17 Timer / Counter cell
L / PIB / PQB / PAB byte/word PIW 256 Periphery (direct I/O, no image)

Bit granularity: a byte contains bits 0..7, e.g. IB 200 = I 200.0 through I 200.7. Word granularity starts on byte boundaries: IW 200 occupies bytes 200 and 201, little-endian. A double word must also start on a byte boundary. Misaligned word/double-word access is one of the classic S7-300 fault triggers (SF LED lit, diagnostic buffer entry "Area length error").

3. Symbolic Addressing and Symbol Priority

A symbol is a name in the symbol table (Options > Symbol Table in SIMATIC Manager) bound to one absolute address. Symbols can be local to a block (declared in the block's variable declaration table, VAR_INPUT, VAR_IN_OUT, VAR_STATIC, VAR_TEMP) or global. Toggle the editor's display mode in the LAD/STL/FBD view with View > Display > Symbolic Representation. The default addressing priority is set under Options > Customize > STL/LAD/FBD > Address priority:

  • Absolute priority - the editor will insert a new variable's absolute address as the lookup key, even if a symbol exists.
  • Symbolic priority - the editor will insert the symbol; the absolute address is shown only as a hover tooltip.

Symbolic priority does not hide the absolute address from the Cross-Reference; it only changes what the editor writes on new code. The compiled S7 program always stores the absolute address. See the official Siemens KB article How can you access unstructured data types bit-by-bit, byte-by-byte or word-by-word and symbolically in STEP 7 (TIA Portal)? for the equivalent syntax in TIA Portal; the same address-priority concept applies, with the addition of the "default tag table" mechanism.

Symbolic priority and instance DB growth: when symbolic priority is set, inserting a new VAR into a UDT or into an FB declaration can shift the absolute offsets of every later variable. The recompile rewrites those absolute offsets, so any absolute-coded block that referenced them will fail to compile until it is updated. This is the mechanism behind the "I don't have to care what the absolute addresses are" benefit of symbolic-priority programming - and the same mechanism that breaks absolute-coded consumers when the declaration is reordered.

4. Direct Addressing - STL Examples

Direct addressing names the address literally in the instruction. The classic AND-AND-output pattern:

A   I 0.0        // AND input bit 0.0 (boolean 1 cycle read of IPI)
A   I 0.1        // AND input bit 0.1
=   Q 12.0       // assign result to output bit 12.0 (OPI write at end of OB1)

The same lines written symbolically (assuming symbol table binds I0.0 = "StartPB", I0.1 = "StopPB", Q12.0 = "ContactorKM1"):

A   "StartPB"
A   "StopPB"
=   "ContactorKM1"

Both forms are direct. Switching display mode in the editor toggles the textual representation; the compiled code is identical. Direct addresses always show up in the Cross-Reference list because their absolute location is known at compile time.

5. Indirect Addressing - Address Registers, Area-Internal and Area-Crossing Pointers

Indirect addressing in STL uses one of the two 32-bit address registers AR1 or AR2. The syntax is [ARn, P#byte.bit]:

// Address registers loaded with a base pointer
LAR1  P#I 0.0       // AR1 points at input byte 0, bit 0 (area code 0x81)
LAR2  P#DBX 100.0   // AR2 points at data-bit 100.0 of the open DB

A   I [AR1, P#0.0]  // A I 0.0
A   I [AR1, P#2.4]  // A I 2.4
=   Q [AR1, P#20.6] // = Q 20.6

The P#byte.bit portion is a 16-bit signed offset; the area identifier comes from the lower 8 bits of the address register. Two pointer formats exist:

Format Width Bits 31..24 Bits 23..16 Bits 15..0 Where it is used
Area-internal (32-bit) 32 bits 0000 0000 Byte number (0..65535) bit pattern: bbbb bbbb bbbx xxxx Inside one area, e.g. inside DI when DI is open
Area-crossing (48-bit in pointer DB/any) 48 bits when stored in a DB double word Area code (0x81=I, 0x82=Q, 0x83=M, 0x84=DB, 0x85=DI, 0x87=L) Byte number (0..65535) bit pattern Crossing areas; pointer stored in a DB so the program can change it at runtime

A pointer to I 0.0 stored in MD 100 looks like DW#16#8100_0000 (area code 0x81, byte 0, bit 0). A pointer to DB 49 DBD 16 stored in DB50.DBD 0 looks like DW#16#8400_0031 + offset, area 0x84 for DB, 0x85 for DI. See the STEP 7 Programming with STL manual, section "Indirect addressing of data blocks".

5.1 Loop Example - Searching a Recipe Table

STL with an indirect pointer used to scan a 100-entry recipe DB for a code typed by the operator:

// AR1 = pointer to the current DBW being scanned
// MW 200 = running index, 0..99
// Recipe number (operator entry) lives in IW 60

L    0
T    MW 200                    // index = 0
LAR1 P#DBX 0.0                 // point at first word of opened DB (DB49)

NEXT:L   DBW [AR1, P#0.0]      // load recipe code
      ==I                       // compare to IW 60
      JC  FOUND                 // jump if equal
      +AR1 P#2.0                // advance one word (16 bits)
      L    MW 200
      L    1
      +I
      T    MW 200               // index +=
      L    MW 200
      L    100
      <I
      JC  NEXT                  // loop while index < 100
      BEA                       // not found: block end

FOUND: ...                     // recipe found, parameter is at AR1

None of the recipe entries DBW[AR1,P#0.0] ever appear in the Cross-Reference list. The only absolute that the lister sees is the base pointer loaded into AR1 (DBX 0.0) and the loop counter (MW 200).

6. Indexed Addressing - The SCL Form

STEP 7 SCL (Structured Control Language) supports arrays with one, two, or three dimensions and an integer index per dimension. A three-axis warehouse location array:

TYPE
  UDT_Location : STRUCT
    Aisle   : INT;     // 1..12
    Column  : INT;     // 1..24
    Shelf   : INT;     // 1..6
    PCode   : STRING[12];
  END_STRUCT;
END_TYPE

VAR
  Location : ARRAY[1..12, 1..24, 1..6] OF UDT_Location;
END_VAR

// Read a product code at runtime:
PCode := Location[Aisle, Column, Shelf];

SCL allows a variable as the index, unlike STL/LAD/FBD which require a literal constant. This is what "indexed" means in the original taxonomy: the array is fixed at edit time, the index is dynamic. Cross-Reference lists the array symbol and the index variables Aisle, Column, Shelf, but not the elements that could be selected - which can exceed 1700 combinations for a 12x24x6 array.

7. Searching for Bits - The Three STEP 7 Workflows

There is no global text "Find in Files" for live blocks in SIMATIC Manager. Engineers coming from RSLogix 500/5000 expect Find All against the project tree; STEP 7 V5.x replaces that with three workflows, each with different coverage.

7.1 Cross-Reference List (preferred default)

  1. Open SIMATIC Manager, navigate to the S7 Program node.
  2. Right-click Blocks and choose Cross-References > Display.
  3. The dialog opens with the entire program pre-selected; click OK to build the list (this can take 30 s to 2 min on a 2000-block project).
  4. The list shows <Address>, <Symbol>, <Block / Location> (FB / FC / OB / DB / instance), <Type> (R = read, W = write, RW = both), and <Language>.
  5. Filter by area: View > Filter or click the column header to sort by address.
  6. Double-click any row to jump directly to the instruction in the source block.

The list is compiled from the offline blocks. Online Cross-Reference (PLC > Cross-Reference) reads the loaded program; coverage is identical if the offline and online content match. If they do not match (an FC was edited online, or a new FB was downloaded), rebuild the offline view with View > Update.

7.2 Go To Location (single-block shortcut)

  1. Open any block (FC, FB, OB, DB) in the LAD/STL/FBD editor.
  2. Right-click on any absolute address in the code and choose Go To > Location.
  3. Type or paste the target absolute address (e.g. DB49.DBX 22.4); Go To opens the block that owns it and places the cursor on the line.

Go To Location only works for direct absolute addresses. The dialog will refuse to follow an indirect operand such as I [AR1, P#0.0] - that is by design, because the absolute address of that operand does not exist at edit time. This is the single most common reason an experienced troubleshoot says "Go To is broken": the source is indirect.

7.3 Generate Source / Block Source as Text Search

  1. In SIMATIC Manager, select the Blocks folder.
  2. Right-click and choose Generate Source from the STL source-file generator (or Blocks > Export Source in older builds).
  3. Give the new source a name (e.g. ProjectDump.src); pick "All blocks" or multi-select specific FCs/FBs/DBs.
  4. Click OK. A text file is created in the Sources folder.
  5. Open the source by double-clicking; the block editor opens it as a text window with the raw STL/SCL, symbol-table references, and absolute addresses inline.
  6. Use the editor's Edit > Find and Replace (Ctrl+F) to search any string - variable name, DB number, symbol fragment.

The block-source export reveals indirect and indexed references because it shows the raw instruction text, which is exactly what the compiled S7 executes. This is the only search path that will find DBW [AR1, P#0.0] in a loop body. It also reveals loop counters (MW 200 in section 5.1) and the constant offsets the programmer used to address array elements manually.

8. Comparison with RSLogix 500 / RSLogix 5000

Engineers migrating from Allen-Bradley software often look for a one-to-one map. The mapping is close but not identical:

Concept RSLogix 500 / 5000 STEP 7 V5.x (S7-300/400) STEP 7 TIA Portal
Boolean input I:0/0 or Local:0:I.Data.0 I 0.0 %I0.0 tag
Boolean output O:0/12.0 or Local:0:O.Data.12 Q 12.0 %Q0.0 tag
Integer tag N7:10 or RecipeIndex MW 20 or "RecipeIndex" PLC tag table
Global search Search > Find on the project tree, including descriptions Cross-Reference + block-source export; no text search across live blocks Search in Project (Ctrl+Shift+F) across all editors
Cross-reference Search > Cross Reference on any tag Cross-Reference list (Blocks container) Inspector > Cross-reference
Indirect addressing Indexed tags, e.g. Recipe[i].Field with i in S:INT [AR1, P#byte.bit] in STL; arrays + variable index in SCL Indexed tags, DB"Rcp".Recipe[i] in SCL
Pointer to area Not a first-class concept; structured tag handles it Area-crossing pointer in a DB double word Variant pointer in SCL

The TIA Portal "Search in Project" is what most engineers coming from RSLogix expect. STEP 7 V5.x cannot do this in a single keystroke; the workflow is Cross-Reference for direct references, Go To Location for known direct addresses, and block-source export for indirect/indexed ones.

9. The "Generic Code" Pattern - Indirect-Only Programs and How to Search Them

Some production programs are written almost entirely with shared data blocks, UDTs, and a dynamically manipulated address register. The pattern, common on configurable conveyor and material-handling lines, looks like this in pseudo-STL:

// OB1 - loop over N similar subsystems
LAR2  P#DBX 0.0                 // base pointer in shared DB
L     0
T     MW 500                    // i = 0

LOOP: L    MW 500
      L    20
      >=I
      JC  DONE
      // manipulate AR2 by +UDT_SIZE for each subsystem:
      +AR2 P#<UDT size>
      // call a single FC that does the work for one subsystem:
      CALL FC 100               // FC uses AR2 as its IN/OUT pointer
      L    MW 500
      L    1
      +I
      T    MW 500
      JU   LOOP

DONE: BEU

Such a program has very few absolute addresses. Every "tag" you want to monitor is a calculated offset inside the shared DB, accessed through AR2. The Cross-Reference will show:

  • The shared DB name and structure UDT (one row each, with the UDT members listed).
  • The base address DBX 0.0 loaded into AR2.
  • The loop counter MW 500.
  • The called block FC 100.

It will not show:

  • Any specific subsystem's I/O.
  • Any specific subsystem's calculated value.
  • Any indexed recipe entry.

To find a value in this kind of program, use the block-source export and search the FC body for the variable name (most generic-code programmers declare the UDT members in the FC's VAR_TEMP via the AR2 pointer, so the source contains the UDT member name as a comment). Monitor the value online with a VAT that points at the calculated address: open a Variable Table, manually compute the offset (subsystem index * UDT size + member offset), and watch that absolute. Example for subsystem 7, member SpeedSP at UDT offset 14 bytes:

// In a VAT, watch: DB47.DBW (7 * UDT_size + 14)
// If UDT_size = 64 bytes: DB47.DBW 462
Commissioning caveat: if the generic code was downloaded from one machine to another and the shared DB was regenerated, the UDT layout is the same but the absolute base of the DB can be different. Always read the DB number from the project, not from a previous VAT.

10. Searching When You Only Have an Alarm Text

HMI alarm messages are usually triggered by a single bit somewhere in the program. When the bit's name is not the alarm text (which is the norm, since the alarm text is built from a string table, not the tag name), use the alarm-side trigger approach:

  1. Open the HMI alarm log and identify the alarm number and time.
  2. From the HMI configuration (WinCC flexible / TIA Portal HMI), find the trigger tag for that alarm - it is a bit symbol (e.g. "Alarm_Motor1_Overtemp").
  3. In STEP 7, right-click the symbol in the symbol table and choose Cross-Reference (or Go To > Usage) to find every block that reads or writes that bit.
  4. From the cross-reference, open the FC/FB/OB that sets the bit and trace back to the cause - typically an analog-input high alarm or a feedback mismatch.

For TIA Portal programs, the equivalent is Inspector > Cross-reference on the tag, and the Search in Project feature (Ctrl+Shift+F) for symbol or text search across all editors.

11. Troubleshooting Matrix - Symptom to Cause

Symptom Likely cause Confirm with Fix
Cross-Reference list is empty for a known address Address is indirect ([AR1,P#...]) or indexed Block-source export and search Document the pointer base; use VAT to watch the calculated offset
Go To Location does nothing on an address Address is indirect Look for square brackets in the instruction Use Cross-Reference on the base symbol or pointer variable
Cross-Reference row says "Instance DB" and the row's block is one of many The tag lives in a UDT, copied to many instance DBs Open the instance DB, check the UDT type Use Find In Source on the UDT member name to find every site that reads it
Symbol does not resolve; absolute works Symbol table not loaded, or symbol deleted Options > Symbol Table opens blank Re-add the symbol; recompile and download
SF LED on, diagnostic buffer says "Area length error writing to DB49.DBW" Loop walked past the end of the DB, pointer incremented off-end Online > Monitor/Modify the pointer (AR1) and loop counter Add the loop bound to the pointer load; check DB length matches the index range
Output bit works for some I/O but not all Code is generic with AR2 manipulation; one subsystem has wrong UDT instance Watch the AR2 register in OB1 while scanning Verify shared-DB length matches the configured subsystem count
Address is correct in symbol table but program looks for a different address Symbolic priority active; code is absolute; symbol was added later Open the block, switch display mode between absolute and symbolic Re-insert the symbol in the block to force re-link
Search for a variable returns nothing Variable is a multi-character symbol fragment; default find is case-sensitive Try a partial match Edit > Find and Replace with "Match case" off and "Whole word" off

12. Verification Checklist

Use this list before signing off a search-and-trace exercise on an S7-300/S7-400 system:

  • Cross-Reference list is regenerated after every offline edit (View > Update).
  • Symbol table is saved (Symbol Table > Save) and reloaded into the S7 program before going online.
  • Online and offline blocks match - PLC > Compare reports no offline/online differences.
  • Any indirect loop is bounded; pointer load is re-initialised at the top of each iteration.
  • Any indexed array index is range-checked before access; out-of-range access produces a diagnostic-buffer "Area length error" and an SF LED on the CPU.
  • Block-source export is regenerated before any cross-team search, so the source mirrors the current offline program.

13. Field Notes

The decision to write a program symbolically or absolutely is not a free choice once the project is in service. A user program that was written symbolically is easy to maintain with a symbol table; a user program that was written with heavily indirect logic and a shared DB is maintainable only by someone who understands the pointer arithmetic. Most third-party service companies that troubleshoot Siemens programs prefer direct-symbolic code precisely because every tag is reachable through the Cross-Reference list. Generic-code patterns save development time on a 20-subsystem line but multiply the time required for a one-off fault on a single subsystem.

For the troubleshoot-first reader, the practical sequence is: (1) confirm the address mode (direct absolute, direct symbolic, indirect, or indexed) by opening the suspect block, (2) use Cross-Reference if direct, (3) use Go To Location for a quick jump between direct addresses, (4) export the block source and text-search for indirect references, and (5) use a VAT with a calculated offset to monitor the live value of any indirect or indexed access. With those five moves, no tag in any well-formed STEP 7 program is unreachable.

FAQ

Why does the Cross-Reference list not show a tag I know is in the program?

The tag is most likely addressed indirectly with [AR1, P#byte.bit] or indexed through an SCL array. The Cross-Reference list only contains addresses known at compile time. Export the blocks to a source file (Generate Source) and search the source text for the symbol or UDT member name.

How do I jump from one address to another in a live block without rebuilding the Cross-Reference?

Right-click the address in the LAD/STL/FBD editor and choose Go To > Location. Type the target absolute address (e.g. DB49.DBX 22.4) and the editor opens the owning block at that line. The shortcut works only for direct addresses.

What is the difference between indirect and indexed addressing in STEP 7?

Indirect uses a 32- or 48-bit pointer loaded into address register AR1 or AR2, with a fixed offset, and is available in STL. Indexed uses an integer expression as the array index and is only available in SCL. Indirect can cross areas (I, Q, M, DB); indexed stays within one declared array.

Can I search the entire STEP 7 V5.x project for a string, the way RSLogix 500 searches the project tree?

Not in a single step. The workflow is Cross-Reference for direct references, Go To Location for direct addresses, and Generate Source for indirect/indexed references. TIA Portal adds the "Search in Project" function (Ctrl+Shift+F) that covers all three cases.

How do I monitor a value that is addressed indirectly in a VAT?

Compute the absolute address from the base pointer plus the loop index times the UDT/array size plus the member offset, then enter that absolute in the VAT. Example: subsystem 7, member at UDT offset 14, UDT size 64 -> watch DB47.DBW 462. Recompute if the UDT layout or the shared DB number changes between projects.

Back to blog