Finding Unused DM Addresses in CX-Programmer Cross-Reference

James Nishida12 min read
CJ/CP SeriesOmronTutorial / 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: Why Simple Search Fails in CX-Programmer

Locating unused Data Memory (DM) addresses in an Omron CS/CJ/CP-series PLC project is a routine task during commissioning, retrofit, or memory-map audit work. Engineers migrating from Allen-Bradley RSLogix 500 expect a "Usage" column inside the Data Files editor that immediately highlights free words. CX-Programmer (the PLC programming editor shipped inside the CX-One suite, catalog CX-ONE-ALC-[-V4]) does not expose that single-button view. The Find / Replace dialog (Ctrl+F) searches for a literal token and returns a "Not found" message when the address is absent from the visible source — but that result is a false negative whenever the address sits inside a block instruction, a function block, or an indirectly-referenced pointer chain.

The reliable method is the Cross-Reference Report, accessed from the View menu. This report walks the entire compiled project, enumerates every operand that the ladder/ST program actually touches, and reports a count plus a list of rung/step locations. The remainder of the DM area is unused by definition.

The Failure Mode of the Find Dialog

The Find function performs a literal text scan of the active section. It cannot infer that a destination range has been written because an instruction's source operand is mentioned. The classic failure case is the BSET block-set instruction:

BSET #0000 DM0001 DM0005

Mnemonic expansion: write the constant #0000 to every word from DM0001 through DM0005. A search for the literal token DM0003 returns no hit, yet DM0003 has been written at runtime. The same blind spot applies to BCNT (block count), XFER (block transfer), DIST (single-word distribute to a 16-bit table), COLL (collect from a 16-bit table), MOVB (move bit), MOVD (move digit), SRCH, FCS, MAX, MIN, SUM, and the PID instruction, which transparently consumes DM word N through N+38 for its control block.

Critical: A "Not found" from the Find dialog proves only that the address does not appear as a literal token in source text. It does not prove the address is unused at runtime. Use Cross-Reference as the source of truth.

Block Instructions That Implicitly Consume Address Ranges

The following table lists common instructions whose operand syntax hides a range. Each is a common source of false negatives when relying on plain search.

Instruction Mnemonic Range Consumed Hidden Words
Block Set BSET S D N D .. D+(N-1) DM0 .. DM(N-1)
Block Transfer XFER N S D S .. S+(N-1) and D .. D+(N-1) 2×N words
Block Compare BCMP S T D N T (16-word table) and D (32-word result) 48 words
Data Collect COLL S O D C S base + O offsets Up to C words
Data Distribute DIST S O D C D base + O offsets Up to C words
PID Control PID S C C .. C+38 39 control words
Table Compare TCMP S T R T (16 words) and R (1 result) 17 words
Stack Record SSET/SDEL/SREAD P P .. P+(N-1) N words
Memory Block Move FUN(98) variant Source and destination blocks Configurable

For each instruction, the displayed operand in the CX-Programmer rung is the first word of the range. The remaining words are referenced implicitly by the PLC firmware and are not visible in the operand column of the rung. Only the Cross-Reference tool, which is built on top of the compiled symbol table, marks them.

Indirect Addressing and the Undecidable Case

Omron PLCs support indirect DM addressing with the *DM prefix, e.g. MOV #0010 *DM0000 writes the constant 16 to the DM address whose location is held in DM0000. The target is therefore data-dependent: it can be any word from DM0000 to D32767 (CJ2H) depending on the value the program stores into DM0000 at runtime, or it can be sourced from a Host Link command issued by an NS-series HMI.

From a static-analysis standpoint, this creates an undecidable problem: no tool can prove which words are reachable without simulating the program's entire data-flow. CX-Programmer's Cross-Reference tool reports the indirect operand (*DM0000) but not its resolved target. The conservative practice is to treat the entire DM area potentially reachable from an indirect pointer as possibly used, and reserve it explicitly in the I/O allocation document.

The same logic applies to index registers IR0..IR15, DR data registers (the DR/IR pair forms a 32-bit pointer on CJ2), and the [@D] and *D forms used in structured text.

The Cross-Reference Report: Definitive Solution

The Cross-Reference Report is generated from the compiled project database. It enumerates every address referenced in the program — including those referenced indirectly through block instructions — and produces a count plus a list of locations. To launch it:

  1. Open the project in CX-Programmer and ensure the project compiles cleanly (Ctrl+F7 or Program > Compile). Cross-Reference against a project with compile errors will be incomplete.
  2. From the menu bar, choose View > Cross-Reference Report (keyboard shortcut Ctrl+Shift+F in CX-Programmer v9.x). In older v6.x builds the path is Tools > Cross Reference.
  3. When prompted, select the address type. For DM-area audits choose DM; for full memory-map coverage choose All Addresses.
  4. Select the area(s) of interest. To audit the entire DM bank, enter D00000 as the start and the maximum DM word for the target CPU as the end (e.g. D32767 for CJ2H, D24575 for CJ1M, D8191 for CP1E/CP1L).
  5. Click Generate. The report opens in a child window.

Step-by-Step: Reading the Cross-Reference Tabular View

The default report has two complementary views, switchable with the toolbar buttons.

Tree View (Hierarchical)

The left pane lists every address that the project references, grouped by area. Expanding D shows the full DM bank. Each node displays the symbol (if defined), the address, and the number of usages. Right-click a leaf node to jump to the rung.

Tabular View (Spreadsheet)

Use the toolbar toggle to switch to the tabular view. Columns include:

Column Meaning
Address Full operand including area prefix (e.g. D00010)
Name Symbol, or blank if no symbol has been assigned
Usage Count Total number of times the address appears in the program (read + write + on-screen reference)
Read/Write Whether the contact is normally-open (read), normally-closed (read), or used as a destination operand (write)
Instruction The function/mnemonic of the rung where the reference occurs
Location Section name, rung number, and step number, e.g. Section1 / Rung 42 / Step 5
Comment Rung comment, if any

Sort by the Address column to walk the bank in numerical order. Rows with a non-zero Usage Count are referenced; rows that do not appear at all are unused. A usage count of 1 from a block-instruction base word (e.g. D00100 with usage 1 inside a BSET D00100 D00110) is the canonical signal that the range D00100..D00109 is also live — the Cross-Reference tool's pre-processing for the block-instruction set adds the implicit range markers automatically in CX-Programmer v7.0 and later.

Exporting and Diffing the Report

For documentation and audit purposes, the report should be exported rather than read on-screen:

  1. In the Cross-Reference window, choose File > Save As.
  2. Select a CSV output. The file contains the same columns as the tabular view plus a header row with the project name, PLC model, and generation timestamp.
  3. Import the CSV into Excel, sort by Address, and apply a conditional rule that highlights rows where Usage Count = 0. Those rows identify unused DM words.
  4. For projects that must track I/O allocation over time, store the CSV in version control (e.g. as IO_Allocation_DM.csv) and diff it against the previous revision. New entries flag recently added references; deleted entries flag recently removed code.
Tip: Pair the Cross-Reference CSV with a separate Allocation worksheet that reserves ranges for vendors, options, and future expansion. The Cross-Reference proves what is used; the Allocation worksheet proves what is reserved. The two together form a complete memory map.

CS/CJ Series DM Search Tips

The CX-Programmer Find dialog has two quirks that bite engineers coming from RSLogix:

  • Do not add the M prefix when searching for DM on CS/CJ/CP-series. Enter D00010, not DM00010. The DM mnemonic is C-series (C200H, CQM1) syntax. On CS/CJ, the area is just D. Adding the M produces "Not found" even for addresses that are obviously referenced.
  • Search scope must be set to Addresses, not Strings or Symbols. The Scope drop-down in the Find dialog is at the bottom of the Find dialog and defaults to All on a fresh install. Set it explicitly to Address when hunting for DM usage.

These two adjustments make the Find dialog usable for spot checks; for complete coverage the Cross-Reference report is still required.

Comparison with RSLogix 500 Cross-Reference

Engineers familiar with Allen-Bradley RSLogix 500 will recognise the overall flow but should note the differences in the table below.

Capability RSLogix 500 (AB) CX-Programmer (Omron)
Per-file usage view Data Files > double-click file > Usage column shows "Used by X rungs" No equivalent single-file view; use Cross-Reference Report from the View menu
Block-instruction range expansion Implicit for FAL, COP, FLL, etc. Implicit for BSET, XFER, BCNT, PID, etc., but only visible in the Cross-Reference tabular view
Indirect-address handling Statically enumerated for indexed addressing with literal offset tables; opaque for N[x] pointers Opaque: *DM references are reported as the pointer itself, not the targets
Export format CSV via Tools > Database > Export CSV from Cross-Reference Save As
Live cross-reference update Updates on every save in RSLogix 500 v8 and later Generated on demand; regenerate after every program change
Symbol integration Symbol table shown inline Symbol column in tabular view; global/Local symbol tables supported

Best Practices: Maintaining an I/O Allocation Document

The single most reliable way to find unused addresses is to prevent the question from arising by maintaining an I/O allocation document alongside the program. The recommended structure for a DM bank on a CJ2M-CPU33 is shown below in compressed form.

Range Allocation Owner Last Reviewed
D00000 – D00099 System / PLC Setup OEM 2024-03-12
D00100 – D00199 Recipe 1 parameters Process Eng 2024-03-12
D00200 – D00299 Recipe 2 parameters Process Eng 2024-03-12
D00300 – D00338 PID loop 1 control block Controls Eng 2024-03-12
D00340 – D00399 Reserved for PID loop 2 Controls Eng 2024-03-12
D00400 – D00499 Free for project expansion
D00500 – D32767 Spare / vendor reserved

After every program change, regenerate the Cross-Reference Report, export to CSV, and update the Owner and Last Reviewed columns to match. Discrepancies between the document and the report indicate a stale allocation entry — usually a sign that an engineer added code without updating the document.

Verification: A Worked Example

Consider a small project that contains the following three rungs:

Rung 1: LD 0.00
        MOV #100 D00200
Rung 2: BSET #0 D00210 D00220
Rung 3: PID D00300 D00340

A naive search for D00215 returns "Not found". A search for D00350 also returns "Not found". Yet the Cross-Reference Report will show:

  • D00200 used 1 time (Rung 1, MOV destination)
  • D00210 used 1 time (Rung 2, BSET start)
  • D00211 used 1 time (Rung 2, implicit)
  • ... through D00220 used 1 time (Rung 2, implicit)
  • D00300 used 1 time (Rung 3, PID set-point word)
  • D00301..D00338 used 1 time each (Rung 3, implicit PID control block)
  • D00340 used 1 time (Rung 3, PID control word)

Any DM address not in this list (for example D00150, D00500, D10000) is provably unused and is a candidate for the next feature to be added.

Limitations and Edge Cases

  • Auto-allocation of symbols. When Tools > Options > Symbol > Auto-allocate is enabled, CX-Programmer assigns the next free DM address to any new tag with no explicit address. This masks the developer's intent and makes the Cross-Reference list noisy with auto-generated entries. Disable auto-allocation on serious projects and assign addresses explicitly from the allocation document.
  • Function Blocks (FB). Function blocks have their own internal variable space. Cross-Reference against an instance does not always descend into the FB body. Open the FB and run a local Cross-Reference to enumerate internal usage.
  • Task programs. Cyclic, scheduled, and extra cyclic tasks each have their own program sections. Run Cross-Reference on the project level to include all tasks.
  • SFC programs. The Cross-Reference Report does not always cover SFC transitions and actions cleanly. Generate the report on the corresponding ladder sections to cover all reachable code paths.
  • Indirect addressing. As discussed, *DM targets are undecidable. The report will list *DM0000 but not the words DM0000 may point to. Treat the entire plausible range as reserved.

References and Verification Steps

To verify the report against the live PLC, transfer the project to a fresh PLC, set the CPU to PROGRAM mode, and use the CX-Programmer Memory Component > DM view to inspect actual word values. Words in the unused range should read the battery-backed default (0000) for non-retained areas, or the value last programmed for retained DM (D12000..D32767 on CJ2). For vendor documentation consult the Omron Industrial Automation portal, specifically the CX-Programmer Operation Manual (W446) and the CS/CJ/CP Series Programming Manual (W394).

FAQ

Why does Find return "Not found" for a DM address that the program clearly uses?

Find is a literal text search. Block instructions such as BSET, XFER, BCNT, and PID reference the first word of a range; the remaining words are consumed by the PLC firmware at runtime and are not visible in the source text. Use the Cross-Reference Report (View > Cross-Reference Report) to enumerate all referenced DM words, including implicit range members.

How do I search for a DM address on a CS/CJ-series PLC without missing it?

Enter the address as D00010, not DM00010. The DM prefix is C-series (C200H/CQM1) syntax. On CS/CJ/CP the area is just D. Also set the Find dialog's Scope field to Address rather than leaving it at All.

Can the Cross-Reference tool follow *DM indirect addressing?

No. Indirect targets are data-dependent and cannot be resolved by static analysis. The report lists the pointer operand (for example *DM0000) but not the word it ultimately addresses at runtime. Treat the full plausible target range as reserved, and document the pointer in the I/O allocation worksheet.

What is the largest DM range I should audit at one time?

Limit the report to a single area to keep the output readable. For a CJ2H the DM bank is D00000..D32767; for CJ1M it is D00000..D24575; for CP1E/CP1L it is D00000..D8191. Audit the entire range as one report, then sort the exported CSV by address and isolate contiguous blocks of unused words for new features.

Does the Cross-Reference report cover Function Block instances?

The project-level report lists the instance and its I/O pins, but not the internals of the Function Block body. To enumerate internal usage, open the FB definition, right-click the section, and run a local Cross-Reference against the FB's internal variable area.

Back to blog