COP and CPS Instructions on a ControlLogix: Which One Copies What
A COP with a Length of 20 moved five bytes. The source was Packer_Raw, a SINT[5]; the destination was Packer_Scaled, a DINT[10]; the rung went true on every scan of a 1756-L71 and nothing faulted, nothing latched, nothing appeared in the Minor Faults tab.
The COP and CPS instructions are the two that copy. Length on both of them counts destination elements, never source elements and never bytes, and after Length is turned into a byte count the controller applies two or three more limits and uses the smallest answer it gets. CPT is in this article because people reach for it by mistake: it is an expression evaluator and it does not copy anything.
Everything below is from the operand tables and execution notes in the Logix 5000 general instructions reference, publication 1756-RM018A-EN-P, September 2025.
Length is destination elements, and then the limits cut it down
Take the case at the top and work it in bytes, because bytes are what the instruction deals in.
Packer_Scaled is a DINT array, so a destination element is four bytes, and a Length of 20 is a request for 80 bytes. The destination tag is only ten DINTs, so it can accept 40. The source tag is five SINTs, so it can provide 5. The manual gives those as three separate limits and says the number of bytes copied equals the smaller of them, which here is five, and the consequence is that Packer_Scaled[0] gets four of the source bytes, the low byte of Packer_Scaled[1] gets the fifth, and Packer_Scaled[2] through [9] are exactly as they were. That is not an error state. The instruction did what it was asked; the request was simply larger than the memory at either end of it, and a COP has no way to tell you so.
There is no .ER bit on a COP. There is no control structure at all.
Sizing the Length from the source array instead of the destination is the habit that produces this.

The three limits from the reference manual, applied to one instruction. The bottom row is the one that decides it here, and it is the one that does not apply on every controller family.
Now the part that changes depending on what is in the rack. The source-tag limit is printed for the CompactLogix 5380, ControlLogix 5580 and ControlLogix 5590 families, and their Compact GuardLogix and GuardLogix equivalents. It is not printed for the 5370 and 5570 families. Read that list literally and a 1756-L71 asked for 80 bytes out of a five-byte source has 40 bytes of destination to fill and only five bytes of tag to fill it from, and the remaining 35 come from whatever the controller has stored next to Packer_Raw. I have not deliberately provoked that on a 5570 to watch what lands, so treat this as the manual’s wording rather than as a measurement — but it is a good enough reason to size the Length from the destination tag every time rather than from habit.
The rule that does hold everywhere: the instruction can write past the end of a member array inside a structure, but it will never write past the end of the base tag.
That distinction is worth reading twice if you keep recipes in UDTs. A Recipe[12].Steps member array with eight elements does not stop a COP at eight; the base tag is Recipe[12], and everything after .Steps
What CPS buys, and the bill it sends
One thing, and it is worth stating exactly: tasks that try to interrupt a CPS are delayed until it is done.
That is the entire difference. COP and CPS copy the same bytes in the same order with the same Length arithmetic; CPS additionally holds off the task switch, so what arrives at the destination is one image of the source from one instant, rather than a mixture of two. The reference manual puts four cases in the column that says to pick CPS: the source or destination is a produced tag, or a consumed tag, or I/O data, or data another task can overwrite, plus the case of a non-atomic tag written by a remote device. Every one of those is the same underlying problem — something other than this routine can change the data halfway through — and the failure it produces is nasty precisely because it is arithmetically plausible. A 100-element DINT buffer read by a 20 ms periodic task while a continuous task is COPing into it yields totals that are 40 elements from this second and 60 from the last one, and no element in it is wrong on its own.
No element in that buffer is wrong. The relationship between them is what broke.

Drawn from the execution note that interrupting tasks are delayed until a CPS is done. The left side is the defect; the right side is the jitter you accept to remove it.
The bill is on the other side of that sentence. Your high-priority task ran late, by however long the copy takes, and that delay is added to its worst case whether or not it ever actually collides with the copy. The manual does not print a microseconds-per-byte figure in the instruction reference — it sends you to the ControlLogix system user manual to estimate CPS execution time — so the honest procedure is to measure it: put a GSV on the task’s MaxScanTime, run the machine, and look again after the CPS is in. If your motion or safety task shares the controller, that number belongs in the same conversation as scan time and cycle time.
And the dead end, because everybody reaches it: swapping every COP in the project for CPS does not make the project safer.
It makes every copy an uninterruptible block, including the ones inside the continuous task that nothing else was ever going to touch, and the cost lands on the tasks that had a deadline. Use CPS where something else can write the data. Use COP everywhere else, which on most projects is most places.
There is one more place this matters, and it is easy to miss because it looks like a language feature rather than a copy. Program parameters on a 5380 or 5580 are passed by value, and the program parameters manual, 1756-PM021E-EN-P, says the copy for a BOOL, SINT, INT, DINT, LINT or REAL parameter will not be interrupted — while the copy of any other predefined or user-defined type may be interrupted by a task switch. A UDT Output parameter can be torn the same way a COP can. The same manual names CPS as the thing to buffer with if you need that not to happen.
CPT is not a copy, and it is not available where you want it
The Compute instruction takes two operands: a Dest, which is one tag, and an Expression, which is an arithmetic sentence made of tags, literals and operators.

The Expression box is the instruction. Nothing in a CPT moves a block of memory, and the last two rows are why you will not find it in a Structured Text routine.
Nothing in a CPT has a Length, because there is nothing in it to count.
Three things in that table cost people time. The first is promotion: if any operand in the expression is REAL, or the Dest is REAL, or the expression uses SIN, COS, TAN, ASN, ACS, ATN, LN, LOG, DEG or RAD, every non-REAL operand is converted to REAL before the arithmetic happens — and if anything involved is LREAL, the whole thing goes to LREAL instead. The second is precedence: nine levels, with parentheses first, functions second, ** third, and AND, XOR, OR at the bottom in that order, with equal-order operations left to right. A + B / C is not (A + B) / C and never was. The third is availability: CPT exists in ladder only, not in Function Block and not in Structured Text, because in Structured Text you would write the expression as an assignment and the instruction would have nothing to add. If you are moving that way, Structured Text for people who write ladder covers the six constructs that replace most of this.
Parentheses cost nothing to type and they are cheaper than an afternoon spent on precedence.

The rows marked are the ones that turn a habit into a defect. COP does not care what your data types are, and CPT never copies anything.
One more instruction, one more trap
CPS(project_data[0], produced_array[0], 100) is the manual’s own example of sending a complete image of 100 DINTs to another controller, and it is the shape you want if you are moving data with produced and consumed tags. Note which side of the copy the CPS is on. It is on the producing side, filling the produced tag in one uninterruptible block, because that is the side where another task can interfere. Putting the CPS on the consuming side instead protects nothing that needed protecting.
Protect the side that writes the tag, not the side that reads it.
What to do before you leave this rung
Open the tag editor next to the instruction and write the byte arithmetic into the rung comment: source element size times source count, destination element size times destination count, and the Length you are asking for. Three numbers, one comment, and the next person to look at it does not have to reconstruct any of it.
Two lines of rung comment, written once, save that reconstruction every single time.
Then decide the COP-or-CPS question with one test — can anything other than this routine write to either tag while the copy is running? If the answer is no, COP, and move on. If it is yes, CPS, and go and measure what it did to the worst-case scan of the task that mattered.