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.
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.
[@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:
- Open the project in CX-Programmer and ensure the project compiles cleanly (
Ctrl+F7or Program > Compile). Cross-Reference against a project with compile errors will be incomplete. - 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.
- When prompted, select the address type. For DM-area audits choose DM; for full memory-map coverage choose All Addresses.
- Select the area(s) of interest. To audit the entire DM bank, enter
D00000as the start and the maximum DM word for the target CPU as the end (e.g.D32767for CJ2H,D24575for CJ1M,D8191for CP1E/CP1L). - 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:
- In the Cross-Reference window, choose File > Save As.
- 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.
- 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.
- 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.
CS/CJ Series DM Search Tips
The CX-Programmer Find dialog has two quirks that bite engineers coming from RSLogix:
-
Do not add the
Mprefix when searching for DM on CS/CJ/CP-series. EnterD00010, notDM00010. TheDMmnemonic is C-series (C200H, CQM1) syntax. On CS/CJ, the area is justD. Adding theMproduces "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:
-
D00200used 1 time (Rung 1, MOV destination) -
D00210used 1 time (Rung 2, BSET start) -
D00211used 1 time (Rung 2, implicit) -
...throughD00220used 1 time (Rung 2, implicit) -
D00300used 1 time (Rung 3, PID set-point word) -
D00301..D00338used 1 time each (Rung 3, implicit PID control block) -
D00340used 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,
*DMtargets are undecidable. The report will list*DM0000but not the wordsDM0000may 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.