Repository: PennyLaneAI/pennylane
Pull request #6839 (2025-01-15 22:05:07)

Deprecate `control_wires` in `ControlledQubitUnitary`
**Context:**
We are in favor of `wires`, just like what we did with `MultiControlledX`.

!Note: this involves a big user-interface change. With PL version <=0.40, for ControlledQubitUnitary, we have arg wires to be the target_wires, and the arg control_wires is a positional arg. To be consistent with all the other interfaces, we would like to alter this into wires to be positional and to represent control_wires+target_wires

Before:
```python
mat = np.eye(2)
control_wires = [0]
wires = [1]
ControlledQubitUnitary(mat, control_wires, wires=wires, control_values=...)
```

After:
```python
mat = np.eye(2)
wires = [0, 1]
ControlledQubitUnitary(mat, wires, control_values=...)
```

where the new `wires` should be the concatenate of old `control_wires` and `wires`

**Description of the Change:**
Besides the modified test files across the whole codebase involving `ControlledQubitUnitary`, we made the following necessary adjustments beyond the `__init__` method:
 - classmethod `_unflatten`, `_primitive_bind_call` and method `_controlled` were adjusted accordingly following the new interface
 - move the `TypeError` regarding empty arg `wires` to the beginning.
 - add a register for `ControlledQubitUnitary` in `bind_new_parameters.py` following the pattern of `CRX`
 - modified the direct calls of `ControlledQubitUnitary` inside src files `metric_tensor.py`, `controlled.py` and `matrix_ops.py`.

### **Eco-System**
This is an inevitable breaking change, so we need to immediately inform the affected repos that depend on latest PennyLane but only apply corresponding changes after this PR gets merged.

- [ ] labs

- [ ] Catalyst: [frontend/test/pytest/device/test_decomposition.py](https://github.com/PennyLaneAI/catalyst/blob/286e51910a39c55130de8c1c6a806818a2bcb14b/frontend/test/pytest/device/test_decomposition.py#L52) https://github.com/PennyLaneAI/catalyst/pull/1483

- [ ] Lightning: PR to fix it https://github.com/PennyLaneAI/pennylane-lightning/pull/1047
 - - [test_gates.py](https://github.com/PennyLaneAI/pennylane-lightning/blob/290c986e7d087e17dbf3f09c6d2dfc8bd738cc08/tests/test_gates.py#L451)
 - - [test_gates_and_expval.py](https://github.com/PennyLaneAI/pennylane-lightning/blob/290c986e7d087e17dbf3f09c6d2dfc8bd738cc08/tests/lightning_tensor/test_gates_and_expval.py#L72) 
 - - [test_measurements_class.py](https://github.com/PennyLaneAI/pennylane-lightning/blob/290c986e7d087e17dbf3f09c6d2dfc8bd738cc08/tests/lightning_qubit/test_measurements_class.py#L814)

- [ ] QML Demos: [tutorial_classically_boosted_vqe.py](https://github.com/PennyLaneAI/qml/blob/c444cec6201338056304d9d5a293f969a3aeec5e/demonstrations/tutorial_classically_boosted_vqe.py#L451); PR to fix it https://github.com/PennyLaneAI/qml/pull/1310

### **Plugins**
- [x] Pennylane-AQT: No instances of deprecated code found.
- [x] Pennylane-Qiskit: No instances of deprecated code found.
- [x] Pennylane-IonQ: No instances of deprecated code found.
- [x] Pennylane-Qrack: No instances of deprecated code found.
- [x] Pennylane-Cirq: No instances of deprecated code found.
- [x] Pennylane-Qulacs: No instances of deprecated code found.

**Benefits:**
Unified interfaces with other controlled ops.

**Possible Drawbacks:**
This is a hard change to deprecate this arg. Would have to introduce errors for testing matrices.

**Related GitHub Issues:**
[sc-80842]

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6839?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: Patch coverage is `90.00000%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 99.59%. Comparing base [(`2147813`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/2147813c93a487c992cdee13bb8b09a2b64ced5a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`1c9c152`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/1c9c1525672f2baffcb96d2c66cd410640bcbf4f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 18 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6839?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/ops/op\_math/controlled\_ops.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6839?src=pr&el=tree&filepath=pennylane%2Fops%2Fop_math%2Fcontrolled_ops.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2NvbnRyb2xsZWRfb3BzLnB5) | 87.50% | [2 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #6839      +/-   ##
==========================================
- Coverage   99.60%   99.59%   -0.01%     
==========================================
  Files         476      476              
  Lines       45237    45199      -38     
==========================================
- Hits        45060    45018      -42     
- Misses        177      181       +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6839?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

JerryChen97: > So this is a valid way to create an operator on master:
> 
> ```
> qml.ControlledQubitUnitary(qml.QubitUnitary(np.eye(2), wires=0), control_wires=[1, 2, 3])
> ```
> 
> But on this branch gives me:
> 
> ```
> TypeError: ControlledQubitUnitary.__init__() missing 1 required positional argument: 'wires'
> ```
> 
> For deprecation cycles, we should continue to support all previous behavior, but just raise a warning giving people time to change. So maybe we can have a defualt value of `None` for `wires`, and if `wires is None`, we warn and populate it with the control wires.

@albi3ro Just recovered the previous behavior support
JerryChen97: There has been some necessary changes made since our approvals yesterday. Just for standard process may I ask approvals again? @albi3ro @astralcai 
JerryChen97: OK all good with current full CI tests + GPU tests. I'll remove the labels for faster CI later
JerryChen97: CodeCov is not working properly

Patch:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index cf5eabe77..2ca63fe33 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -9,6 +9,16 @@ deprecations are listed below.
 Pending deprecations
 --------------------
 
+* The ``ControlledQubitUnitary`` will stop accepting `QubitUnitary` objects as arguments as its ``base``. Instead, use ``qml.ctrl`` to construct a controlled `QubitUnitary`.
+
+  - Deprecated in v0.41
+  - Will be removed in v0.42
+
+* The ``control_wires`` argument in the ``qml.ControlledQubitUnitary`` class is deprecated. Instead, use the ``wires`` argument.
+
+  - Deprecated in v0.41
+  - Will be removed in v0.42
+
 * The property ```MeasurementProcess.return_type``` has been deprecated.
   If observable type checking is needed, please use direct ```isinstance```; if other text information is needed, please use class name, or another internal temporary private member ``_shortname``.
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 9200442bb..0dffc758d 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -74,6 +74,7 @@
   [(#6803)](https://github.com/PennyLaneAI/pennylane/pull/6803)
 
   After constructing a `QNode`,
+
   ```python
   import pennylane as qml
 
@@ -83,8 +84,10 @@
     qml.CNOT([0,1])
     return qml.probs()
   ```
+
   its settings can be modified with `update`, which returns a new `QNode` object. Here is an example
   of updating a QNode's `diff_method`:
+  
   ```pycon
   >>> print(circuit.diff_method)
   best
@@ -158,6 +161,13 @@
 
 <h3>Deprecations 👋</h3>
 
+* The ``ControlledQubitUnitary`` will stop accepting `QubitUnitary` objects as arguments as its ``base``. Instead, use ``qml.ctrl`` to construct a controlled `QubitUnitary`.
+  [(#6840)](https://github.com/PennyLaneAI/pennylane/pull/6840)
+
+* The `control_wires` argument in `qml.ControlledQubitUnitary` has been deprecated.
+  Instead, use the `wires` argument as the second positional argument.
+  [(#6839)](https://github.com/PennyLaneAI/pennylane/pull/6839)
+
 * The `mcm_method` keyword in `qml.execute` has been deprecated. 
   Instead, use the ``mcm_method`` and ``postselect_mode`` arguments.
   [(#6807)](https://github.com/PennyLaneAI/pennylane/pull/6807)
diff --git a/pennylane/gradients/metric_tensor.py b/pennylane/gradients/metric_tensor.py
index 35d0d5632..3238c95de 100644
--- a/pennylane/gradients/metric_tensor.py
+++ b/pennylane/gradients/metric_tensor.py
@@ -73,7 +73,9 @@ def _expand_metric_tensor(
     allow_nonunitary=True,
     aux_wire=None,
     device_wires=None,
-) -> tuple[QuantumScriptBatch, PostprocessingFn]:  # pylint: disable=too-many-arguments
+) -> tuple[
+    QuantumScriptBatch, PostprocessingFn
+]:  # pylint: disable=too-many-arguments, too-many-positional-arguments
     """Set the metric tensor based on whether non-unitary gates are allowed."""
     # pylint: disable=unused-argument,too-many-arguments
 
@@ -88,7 +90,7 @@ def _expand_metric_tensor(
     classical_cotransform=_contract_metric_tensor_with_cjac,
     final_transform=True,
 )
-def metric_tensor(  # pylint:disable=too-many-arguments
+def metric_tensor(  # pylint:disable=too-many-arguments, too-many-positional-arguments
     tape: QuantumScript,
     argnum=None,
     approx=None,
@@ -561,7 +563,8 @@ def _get_gen_op(op, allow_nonunitary, aux_wire):
     except KeyError as e:
         if allow_nonunitary:
             mat = qml.matrix(qml.generator(op)[0])
-            return qml.ControlledQubitUnitary(mat, control_wires=aux_wire, wires=op.wires)
+            wires = [aux_wire, *op.wires]
+            return qml.ControlledQubitUnitary(mat, wires=wires)
 
         raise ValueError(
             f"Generator for operation {op} not known and non-unitary operations "
diff --git a/pennylane/ops/functions/bind_new_parameters.py b/pennylane/ops/functions/bind_new_parameters.py
index f6f57ff59..fe33a1d35 100644
--- a/pennylane/ops/functions/bind_new_parameters.py
+++ b/pennylane/ops/functions/bind_new_parameters.py
@@ -163,6 +163,7 @@ def bind_new_parameters_copy(op, params: Sequence[TensorLike]):  # pylint:disabl
 @bind_new_parameters.register(qml.CRZ)
 @bind_new_parameters.register(qml.CRot)
 @bind_new_parameters.register(qml.ControlledPhaseShift)
+@bind_new_parameters.register(qml.ControlledQubitUnitary)
 def bind_new_parameters_parametric_controlled_ops(
     op: Union[qml.CRX, qml.CRY, qml.CRZ, qml.CRot, qml.ControlledPhaseShift],
     params: Sequence[TensorLike],
diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py
index a52e73614..b690ff11c 100644
--- a/pennylane/ops/op_math/controlled.py
+++ b/pennylane/ops/op_math/controlled.py
@@ -339,7 +339,10 @@ def _try_wrap_in_custom_ctrl_op(op, control, control_values=None, work_wires=Non
 
     if isinstance(op, qml.QubitUnitary):
         return qml.ControlledQubitUnitary(
-            op, control_wires=control, control_values=control_values, work_wires=work_wires
+            op.matrix(),
+            wires=control + op.wires,
+            control_values=control_values,
+            work_wires=work_wires,
         )
 
     return None
diff --git a/pennylane/ops/op_math/controlled_ops.py b/pennylane/ops/op_math/controlled_ops.py
index dfb145055..e8490d9c3 100644
--- a/pennylane/ops/op_math/controlled_ops.py
+++ b/pennylane/ops/op_math/controlled_ops.py
@@ -34,17 +34,31 @@ from .controlled_decompositions import decompose_mcx
 INV_SQRT2 = 1 / qml.math.sqrt(2)
 
 
+def _deprecate_control_wires(control_wires):
+    if control_wires != "unset":
+        warnings.warn(
+            "The control_wires input to ControlledQubitUnitary is deprecated and will be removed in v0.42. "
+            "Please note that the second positional arg of your input is going to be the new wires, following wires=controlled_wires+target_wires, where target_wires is the optional arg wires in the legacy interface.",
+            qml.PennyLaneDeprecationWarning,
+        )
+
+
 # pylint: disable=too-few-public-methods
 class ControlledQubitUnitary(ControlledOp):
-    r"""ControlledQubitUnitary(U, control_wires, wires, control_values)
-    Apply an arbitrary fixed unitary to ``wires`` with control from the ``control_wires``.
+    r"""ControlledQubitUnitary(U, wires)
+    Apply an arbitrary fixed unitary matrix ``U`` to ``wires``. If ``n = len(wires) `` and ``U`` has ``k`` wires, then the first ``n - k`` from ``wires`` serve as control, and ``U`` lives on the last ``k`` wires.
+
+    .. warning::
+
+        The ``control_wires`` argument is deprecated and will be removed in
+        v0.42. Please use the ``wires`` argument instead.
 
     In addition to default ``Operation`` instance attributes, the following are
     available for ``ControlledQubitUnitary``:
 
-    * ``control_wires``: wires that act as control for the operation
+    * ``control_wires``: (deprecated) wires that act as control for the operation
+    * ``wires``: wires of the final controlled unitary, consisting of control wires following by target wires
     * ``control_values``: the state on which to apply the controlled operation (see below)
-    * ``target_wires``: the wires the unitary matrix will be applied to
     * ``work_wires``: wires made use of during the decomposition of the operation into native operations
 
     **Details:**
@@ -59,9 +73,10 @@ class ControlledQubitUnitary(ControlledOp):
             operation. If passing a matrix, this will be used to construct a QubitUnitary
             operator that will be used as the base operator. If providing a ``qml.QubitUnitary``,
             this will be used as the base directly.
-        control_wires (Union[Wires, Sequence[int], or int]): the control wire(s)
-        wires (Union[Wires, Sequence[int], or int]): the wire(s) the unitary acts on
-            (optional if U is provided as a QubitUnitary)
+        wires (Union[Wires, Sequence[int], or int]): the wires the full
+        controlled unitary acts on, composed of the controlled wires followed
+        by the target wires
+        control_wires (Union[Wires, Sequence[int], or int]): (deprecated) the control wire(s)
         control_values (List[int, bool]): a list providing the state of the control qubits to
             control on (default is the all 1s state)
         unitary_check (bool): whether to check whether an array U is unitary when creating the
@@ -75,16 +90,9 @@ class ControlledQubitUnitary(ControlledOp):
     both wires ``0`` and ``1``:
 
     >>> U = np.array([[ 0.94877869,  0.31594146], [-0.31594146,  0.94877869]])
-    >>> qml.ControlledQubitUnitary(U, control_wires=[0, 1], wires=2)
+    >>> qml.ControlledQubitUnitary(U, wires=[0, 1, 2])
     Controlled(QubitUnitary(array([[ 0.94877869,  0.31594146],
-       [-0.31594146,  0.94877869]]), wires=[2]), control_wires=[0, 1])
-
-    Alternatively, the same operator can be constructed with a QubitUnitary:
-
-    >>> base = qml.QubitUnitary(U, wires=2)
-    >>> qml.ControlledQubitUnitary(base, control_wires=[0, 1])
-    Controlled(QubitUnitary(array([[ 0.94877869,  0.31594146],
-       [-0.31594146,  0.94877869]]), wires=[2]), control_wires=[0, 1])
+        [-0.31594146,  0.94877869]]), wires=[2]), control_wires=[0, 1])
 
     Typically, controlled operations apply a desired gate if the control qubits
     are all in the state :math:`\vert 1\rangle`. However, there are some situations where
@@ -96,11 +104,11 @@ class ControlledQubitUnitary(ControlledOp):
     wire ``3`` conditioned on three wires where the first is in state ``0``, the
     second is in state ``1``, and the third in state ``1``, we can write:
 
-    >>> qml.ControlledQubitUnitary(U, control_wires=[0, 1, 2], wires=3, control_values=[0, 1, 1])
+    >>> qml.ControlledQubitUnitary(U, wires=[0, 1, 2, 3], control_values=[0, 1, 1])
 
     or
 
-    >>> qml.ControlledQubitUnitary(U, control_wires=[0, 1, 2], wires=3, control_values=[False, True, True])
+    >>> qml.ControlledQubitUnitary(U, wires=[0, 1, 2, 3], control_values=[False, True, True])
     """
 
     num_wires = AnyWires
@@ -115,31 +123,41 @@ class ControlledQubitUnitary(ControlledOp):
     grad_method = None
     """Gradient computation method."""
 
+    def _flatten(self):
+        return (self.base.data[0],), (self.wires, tuple(self.control_values), self.work_wires)
+
     @classmethod
     def _unflatten(cls, data, metadata):
-        return cls(
-            data[0], control_wires=metadata[0], control_values=metadata[1], work_wires=metadata[2]
-        )
+        return cls(data[0], wires=metadata[0], control_values=metadata[1], work_wires=metadata[2])
 
     # pylint: disable=arguments-differ, too-many-arguments, unused-argument, too-many-positional-arguments
     @classmethod
     def _primitive_bind_call(
         cls,
         base,
-        control_wires: WiresLike,
-        wires: WiresLike = (),
+        control_wires: WiresLike = "unset",
+        wires: WiresLike = None,
         control_values=None,
         unitary_check=False,
         work_wires: WiresLike = (),
     ):
-        wires = Wires(() if wires is None else wires)
+        _deprecate_control_wires(control_wires)
         work_wires = Wires(() if work_wires is None else work_wires)
-
-        if hasattr(base, "wires") and len(wires) != 0:
+        if hasattr(base, "wires"):
             warnings.warn(
-                "base operator already has wires; values specified through wires kwarg will be ignored."
+                "QubitUnitary input to ControlledQubitUnitary is deprecated and will be removed in v0.42. "
+                "Instead, please use a full matrix as input, or try qml.ctrl for controlled QubitUnitary.",
+                qml.PennyLaneDeprecationWarning,
             )
-            wires = Wires(())
+            base = base.matrix()
+
+        if control_wires == "unset":
+
+            return cls._primitive.bind(
+                base, wires=wires, control_values=control_values, work_wires=work_wires
+            )
+        # Below is the legacy interface, where control_wires provided
+        wires = Wires(() if wires is None else wires)
 
         all_wires = control_wires + wires
         return cls._primitive.bind(
@@ -150,35 +168,58 @@ class ControlledQubitUnitary(ControlledOp):
     def __init__(
         self,
         base,
-        control_wires: WiresLike,
-        wires: WiresLike = (),
+        control_wires: WiresLike = "unset",
+        wires: WiresLike = None,
         control_values=None,
         unitary_check=False,
         work_wires: WiresLike = (),
     ):
-        wires = Wires(() if wires is None else wires)
+        _deprecate_control_wires(control_wires)
         work_wires = Wires(() if work_wires is None else work_wires)
-        control_wires = Wires(control_wires)
 
-        if hasattr(base, "wires") and len(wires) != 0:
+        if hasattr(base, "wires"):
             warnings.warn(
-                "base operator already has wires; values specified through wires kwarg will be ignored."
+                "QubitUnitary input to ControlledQubitUnitary is deprecated and will be removed in v0.42. "
+                "Instead, please use a full matrix as input.",
+                qml.PennyLaneDeprecationWarning,
             )
-            wires = Wires(())
-
-        if isinstance(base, Iterable):
-            if len(wires) == 0:
-                if len(control_wires) > 1:
-                    num_base_wires = int(qml.math.log2(qml.math.shape(base)[-1]))
-                    wires = control_wires[-num_base_wires:]
-                    control_wires = control_wires[:-num_base_wires]
-                else:
-                    raise TypeError(
-                        "Must specify a set of wires. None is not a valid `wires` label."
-                    )
-            # We use type.__call__ instead of calling the class directly so that we don't bind the
-            # operator primitive when new program capture is enabled
-            base = type.__call__(qml.QubitUnitary, base, wires=wires, unitary_check=unitary_check)
+            base = base.matrix()
+
+        if control_wires == "unset":
+            if not wires:
+                raise TypeError("Must specify a set of wires. None is not a valid `wires` label.")
+            control_wires = wires[:-1]  # default
+
+            if isinstance(base, Iterable):
+                num_base_wires = int(qml.math.log2(qml.math.shape(base)[-1]))
+                target_wires = wires[-num_base_wires:]
+                control_wires = wires[:-num_base_wires]
+                # We use type.__call__ instead of calling the class directly so that we don't bind the
+                # operator primitive when new program capture is enabled
+                base = type.__call__(
+                    qml.QubitUnitary, base, wires=target_wires, unitary_check=unitary_check
+                )
+            else:
+                raise ValueError("Base must be a matrix.")
+        else:
+            # Below is the legacy interface, where control_wires provided
+            wires = Wires(() if wires is None else wires)
+            control_wires = Wires(control_wires)
+            if isinstance(base, Iterable):
+                if len(wires) == 0:
+                    if len(control_wires) > 1:
+                        num_base_wires = int(qml.math.log2(qml.math.shape(base)[-1]))
+                        wires = control_wires[-num_base_wires:]
+                        control_wires = control_wires[:-num_base_wires]
+                    else:
+                        raise TypeError(
+                            "Must specify a set of wires. None is not a valid `wires` label."
+                        )
+                # We use type.__call__ instead of calling the class directly so that we don't bind the
+                # operator primitive when new program capture is enabled
+                base = type.__call__(
+                    qml.QubitUnitary, base, wires=wires, unitary_check=unitary_check
+                )
 
         super().__init__(
             base,
@@ -191,9 +232,13 @@ class ControlledQubitUnitary(ControlledOp):
     def _controlled(self, wire):
         ctrl_wires = wire + self.control_wires
         values = None if self.control_values is None else [True] + self.control_values
+        base = self.base
+        if isinstance(self.base, qml.QubitUnitary):
+            base = self.base.matrix()
+
         return ControlledQubitUnitary(
-            self.base,
-            control_wires=ctrl_wires,
+            base,
+            wires=ctrl_wires + self.wires,
             control_values=values,
             work_wires=self.work_wires,
         )
diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py
index c5dee1430..2ca5bd9d5 100644
--- a/pennylane/ops/qubit/matrix_ops.py
+++ b/pennylane/ops/qubit/matrix_ops.py
@@ -249,7 +249,7 @@ class QubitUnitary(Operation):
         return [QubitUnitary(pow_mat, wires=self.wires)]
 
     def _controlled(self, wire):
-        return qml.ControlledQubitUnitary(*self.parameters, control_wires=wire, wires=self.wires)
+        return qml.ControlledQubitUnitary(*self.parameters, wires=wire + self.wires)
 
     def label(
         self,


Test patch:
diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py
index cd2aebd78..cbe5fe0aa 100644
--- a/pennylane/devices/tests/test_gates.py
+++ b/pennylane/devices/tests/test_gates.py
@@ -72,7 +72,7 @@ ops = {
     "QubitDensityMatrix": qml.QubitDensityMatrix(np.array([[0.5, 0.0], [0, 0.5]]), wires=[0]),
     "QubitUnitary": qml.QubitUnitary(np.eye(2), wires=[0]),
     "SpecialUnitary": qml.SpecialUnitary(np.array([0.2, -0.1, 2.3]), wires=1),
-    "ControlledQubitUnitary": qml.ControlledQubitUnitary(np.eye(2), control_wires=[1], wires=[0]),
+    "ControlledQubitUnitary": qml.ControlledQubitUnitary(np.eye(2), wires=[1, 0]),
     "MultiControlledX": qml.MultiControlledX(wires=[1, 2, 0]),
     "IntegerComparator": qml.IntegerComparator(1, geq=True, wires=[0, 1, 2]),
     "RX": qml.RX(0, wires=[0]),
diff --git a/pennylane/devices/tests/test_templates.py b/pennylane/devices/tests/test_templates.py
index 183ce593f..3ba19e3d4 100644
--- a/pennylane/devices/tests/test_templates.py
+++ b/pennylane/devices/tests/test_templates.py
@@ -678,7 +678,7 @@ class TestTemplates:  # pylint:disable=too-many-public-methods
         target_wires = range(m + 1)
         estimation_wires = range(m + 1, n + m + 1)
         dev = device(wires=n + m + 1)
-        check_op_supported(qml.ControlledQubitUnitary(np.eye(2), [1], [0]), dev)
+        check_op_supported(qml.ControlledQubitUnitary(np.eye(2), wires=[1, 0]), dev)
 
         @qml.qnode(dev)
         def circuit():
diff --git a/tests/devices/default_tensor/test_default_tensor.py b/tests/devices/default_tensor/test_default_tensor.py
index 26f46ab44..08ae5ef38 100644
--- a/tests/devices/default_tensor/test_default_tensor.py
+++ b/tests/devices/default_tensor/test_default_tensor.py
@@ -65,7 +65,7 @@ operations_list = {
     "CPhaseShift10": qml.CPhaseShift10(1.234, wires=[0, 1]),
     "QubitUnitary": qml.QubitUnitary(np.eye(2), wires=[0]),
     "SpecialUnitary": qml.SpecialUnitary(np.array([0.2, -0.1, 2.3]), wires=1),
-    "ControlledQubitUnitary": qml.ControlledQubitUnitary(np.eye(2), control_wires=[1], wires=[0]),
+    "ControlledQubitUnitary": qml.ControlledQubitUnitary(np.eye(2), wires=[1, 0]),
     "MultiControlledX": qml.MultiControlledX(wires=[1, 2, 0]),
     "IntegerComparator": qml.IntegerComparator(1, geq=True, wires=[0, 1, 2]),
     "RX": qml.RX(1.234, wires=[0]),
diff --git a/tests/ops/functions/conftest.py b/tests/ops/functions/conftest.py
index 413d7cb02..6dfadc4d5 100644
--- a/tests/ops/functions/conftest.py
+++ b/tests/ops/functions/conftest.py
@@ -40,7 +40,7 @@ _INSTANCES_TO_TEST = [
     (qml.sum(qml.X(0), qml.X(0), qml.Z(0), qml.Z(0)), {}),
     (qml.BasisState([1], wires=[0]), {"skip_differentiation": True}),
     (
-        qml.ControlledQubitUnitary(np.eye(2), control_wires=1, wires=0),
+        qml.ControlledQubitUnitary(np.eye(2), wires=[1, 0]),
         {"skip_differentiation": True},
     ),
     (
diff --git a/tests/ops/functions/test_bind_new_parameters.py b/tests/ops/functions/test_bind_new_parameters.py
index 00dc631b6..57bd4e88b 100644
--- a/tests/ops/functions/test_bind_new_parameters.py
+++ b/tests/ops/functions/test_bind_new_parameters.py
@@ -156,11 +156,15 @@ def test_scalar_symbolic_ops(op, new_params, expected_op):
             [-0.5, 0.456],
             qml.ctrl(qml.s_prod(-0.5, qml.RX(0.456, 0)), [1]),
         ),
-        (qml.ControlledQubitUnitary(X, [1], [0]), [Y], qml.ControlledQubitUnitary(Y, [1], [0])),
         (
-            qml.ControlledQubitUnitary(qml.QubitUnitary(X, 0), [1]),
+            qml.ControlledQubitUnitary(X, wires=[1, 0]),
             [Y],
-            qml.ControlledQubitUnitary(qml.QubitUnitary(Y, 0), [1]),
+            qml.ControlledQubitUnitary(Y, wires=[1, 0]),
+        ),
+        (
+            qml.ControlledQubitUnitary(qml.QubitUnitary(X, 0).matrix(), wires=[1, 0]),
+            [Y],
+            qml.ControlledQubitUnitary(qml.QubitUnitary(Y, 0).matrix(), wires=[1, 0]),
         ),
     ],
 )
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 3ef0e9be2..98ed803a6 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -107,7 +107,7 @@ PARAMETRIZED_OPERATIONS = [
     qml.CRot(0.123, 0.456, 0.789, wires=[0, 1]),
     qml.QubitUnitary(np.eye(2) * 1j, wires=0),
     qml.DiagonalQubitUnitary(np.array([1.0, 1.0j]), wires=1),
-    qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[0], control_wires=[2]),
+    qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[2, 0]),
     qml.SingleExcitation(0.123, wires=[0, 3]),
     qml.SingleExcitationPlus(0.123, wires=[0, 3]),
     qml.SingleExcitationMinus(0.123, wires=[0, 3]),
@@ -878,18 +878,20 @@ class TestEqual:
         param_qml = npp.eye(2) * 1j
         param_np = np.eye(2) * 1j
 
+        # ControlledQubitUnitary
         op1 = PARAMETRIZED_OPERATIONS_Remaining[3]
+        wires = [wire + 1, wire]
         param_list = [param_qml, param_torch, param_jax, param_tf, param_np]
         for p1, p2 in itertools.combinations(param_list, 2):
             assert qml.equal(
-                op1(p1, wires=wire, control_wires=wire + 1),
-                op1(p2, wires=wire, control_wires=wire + 1),
+                op1(p1, wires=wires),
+                op1(p2, wires=wires),
                 check_trainability=False,
                 check_interface=False,
             )
             assert not qml.equal(
-                op1(p1, wires=wire, control_wires=wire + 1),
-                op1(p2, wires=wire, control_wires=wire + 1),
+                op1(p1, wires=wires),
+                op1(p2, wires=wires),
                 check_trainability=False,
                 check_interface=True,
             )
@@ -897,14 +899,14 @@ class TestEqual:
         param_qml_1 = param_qml.copy()
         param_qml_1.requires_grad = False
         assert qml.equal(
-            op1(param_qml, wires=wire, control_wires=wire + 1),
-            op1(param_qml_1, wires=wire, control_wires=wire + 1),
+            op1(param_qml, wires=wires),
+            op1(param_qml_1, wires=wires),
             check_trainability=False,
             check_interface=False,
         )
         assert not qml.equal(
-            op1(param_qml, wires=wire, control_wires=wire + 1),
-            op1(param_qml_1, wires=wire, control_wires=wire + 1),
+            op1(param_qml, wires=wires),
+            op1(param_qml_1, wires=wires),
             check_trainability=True,
             check_interface=False,
         )
@@ -1061,21 +1063,22 @@ class TestEqual:
         wire = 0
         param = np.eye(2) * 1j
         op1 = PARAMETRIZED_OPERATIONS_Remaining[3]
+        wires = [wire + 1, wire]
         assert qml.equal(
-            op1(param, wires=wire, control_wires=wire + 1),
-            op1(param, wires=wire, control_wires=wire + 1),
+            op1(param, wires=wires),
+            op1(param, wires=wires),
             check_trainability=False,
             check_interface=False,
         )
         assert not qml.equal(
-            op1(param, wires=wire, control_wires=wire + 1),
-            op1(param * 2, wires=wire, control_wires=wire + 1),
+            op1(param, wires=wires),
+            op1(param * 2, wires=wires),
             check_trainability=False,
             check_interface=False,
         )
         assert not qml.equal(
-            op1(param, wires=wire, control_wires=wire + 1),
-            op1(param, wires=wire + 2, control_wires=wire + 1),
+            op1(param, wires=wires),
+            op1(param, wires=[wire + 1, wire + 2]),
             check_trainability=False,
             check_interface=False,
         )
diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py
index b83e42d8a..d647815e8 100644
--- a/tests/ops/op_math/test_controlled.py
+++ b/tests/ops/op_math/test_controlled.py
@@ -1663,7 +1663,7 @@ custom_ctrl_ops = [
     (
         qml.QubitUnitary(np.array([[0, 1], [1, 0]]), wires=0),
         [1, 2],
-        qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), wires=0, control_wires=[1, 2]),
+        qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), wires=[1, 2, 0]),
     ),
 ]
 
@@ -1773,16 +1773,14 @@ class TestCtrl:
 
         op = qml.ctrl(
             Controlled(
-                qml.ControlledQubitUnitary(
-                    np.array([[0, 1], [1, 0]]), control_wires=[1], wires=[0]
-                ),
+                qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), wires=[1, 0]),
                 control_wires=[2],
                 control_values=[0],
             ),
             control=[3],
         )
         expected = qml.ControlledQubitUnitary(
-            np.array([[0, 1], [1, 0]]), control_wires=[3, 2, 1], wires=[0], control_values=[1, 0, 1]
+            np.array([[0, 1], [1, 0]]), wires=[3, 2, 1, 0], control_values=[1, 0, 1]
         )
         assert op == expected
 
@@ -2131,7 +2129,7 @@ class TestTapeExpansionWithControlled:
             ctrl(qml.QubitUnitary, 1)(M, wires=0)
 
         tape = QuantumScript.from_queue(q_tape)
-        expected = qml.ControlledQubitUnitary(M, control_wires=1, wires=0)
+        expected = qml.ControlledQubitUnitary(M, wires=[1, 0])
         assert equal_list(list(tape), expected)
 
         # causes decomposition into more basic operators
@@ -2143,13 +2141,13 @@ class TestTapeExpansionWithControlled:
         """Test ctrl on ControlledQubitUnitary."""
 
         with qml.queuing.AnnotatedQueue() as q_tape:
-            ctrl(qml.ControlledQubitUnitary, 1)(M, control_wires=2, wires=0)
+            ctrl(qml.ControlledQubitUnitary, 1)(M, wires=[2, 0])
 
         tape = QuantumScript.from_queue(q_tape)
         # will immediately decompose according to selected decomposition algorithm
         tape = tape.expand(1, stop_at=lambda op: not isinstance(op, Controlled))
 
-        expected = qml.ControlledQubitUnitary(M, control_wires=[1, 2], wires=0).decomposition()
+        expected = qml.ControlledQubitUnitary(M, wires=[1, 2, 0]).decomposition()
         assert tape.circuit == expected
 
     @pytest.mark.parametrize(
diff --git a/tests/ops/op_math/test_controlled_decompositions.py b/tests/ops/op_math/test_controlled_decompositions.py
index 25f1e37f5..0fe76c6d3 100644
--- a/tests/ops/op_math/test_controlled_decompositions.py
+++ b/tests/ops/op_math/test_controlled_decompositions.py
@@ -1123,7 +1123,7 @@ class TestMCXDecomposition:
 def test_ControlledQubitUnitary_has_decomposition_correct():
     """Test that ControlledQubitUnitary reports has_decomposition=False if it is False"""
     U = qml.Toffoli(wires=[0, 1, 2]).matrix()
-    op = qml.ControlledQubitUnitary(U, wires=[1, 2, 3], control_wires=[0])
+    op = qml.ControlledQubitUnitary(U, wires=[0, 1, 2, 3])
 
     assert not op.has_decomposition
     with pytest.raises(qml.operation.DecompositionUndefinedError):
@@ -1133,6 +1133,6 @@ def test_ControlledQubitUnitary_has_decomposition_correct():
 def test_ControlledQubitUnitary_has_decomposition_super_False(mocker):
     """Test that has_decomposition returns False if super() returns False"""
     spy = mocker.spy(qml.QueuingManager, "stop_recording")
-    op = qml.ControlledQubitUnitary(np.diag((1.0,) * 8), wires=[2, 3, 4], control_wires=[0, 1])
+    op = qml.ControlledQubitUnitary(np.diag((1.0,) * 8), wires=[0, 1, 2, 3, 4])
     assert not op.has_decomposition
     spy.assert_not_called()
diff --git a/tests/ops/op_math/test_controlled_ops.py b/tests/ops/op_math/test_controlled_ops.py
index 4ae0268f6..a41a9670d 100644
--- a/tests/ops/op_math/test_controlled_ops.py
+++ b/tests/ops/op_math/test_controlled_ops.py
@@ -53,23 +53,92 @@ X_broadcasted = np.array([X] * 3)
 class TestControlledQubitUnitary:
     """Tests specific to the ControlledQubitUnitary operation"""
 
-    def test_wires_is_none(self):
-        """Test that an error is raised if the user provides no target wires for an iterable base operator"""
+    def test_flatten_unflatten(self):
+        """Test that the operation can be flattened and unflattened"""
+        op = qml.ControlledQubitUnitary(np.eye(2), wires=(1, 2, 3))
+        qml.ops.functions.assert_valid(op, skip_differentiation=True)
+
+    def test_noniterable_base(self):
+        """Test that an error is raised if the user provides a non-iterable base operator"""
+        # make a non-iterable, non-QubitUnitary base operator
+        base_op = lambda x: 1 if x == 0 else 0  # pauliX as a mapping
+
+        with pytest.raises(ValueError, match="Base must be a matrix"):
+            qml.ControlledQubitUnitary(base_op, wires=[0, 1])
+
+    def test_deprecation_unitary_base(self):
+        """Test that a deprecation warning is raised when using the QubitUnitary base arg"""
         base_op = [[0, 1], [1, 0]]
+        base_op = QubitUnitary(base_op, wires=1)
 
-        with pytest.raises(TypeError, match="Must specify a set of wires"):
-            qml.ControlledQubitUnitary(base_op, control_wires=1, wires=None)
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="QubitUnitary input to ControlledQubitUnitary is deprecated",
+        ):
+            qml.ControlledQubitUnitary(base_op, wires=[0, 1])
 
     @pytest.mark.jax
     @pytest.mark.usefixtures("enable_disable_plxpr")
-    def test_wires_specified_twice_with_capture(self):
-        """Test that a UserWarning is raised for providing redundant wires with capture enabled"""
-        base = qml.QubitUnitary(X, wires=0)
+    def test_deprecation_unitary_base_primitive_bind(self):
+        """Test that a deprecation warning is raised when using the QubitUnitary base arg in the primitive_bind method"""
+        base_op = [[0, 1], [1, 0]]
+        base_op = QubitUnitary(base_op, wires=1)
+
         with pytest.warns(
-            UserWarning,
-            match="base operator already has wires; values specified through wires kwarg will be ignored.",
+            qml.PennyLaneDeprecationWarning,
+            match="The control_wires input to ControlledQubitUnitary is deprecated",
         ):
-            qml.ControlledQubitUnitary(base, control_wires=[1, 2], wires=3)
+            with pytest.warns(
+                qml.PennyLaneDeprecationWarning,
+                match="QubitUnitary input to ControlledQubitUnitary is deprecated",
+            ):
+                qml.ControlledQubitUnitary(base_op, wires=[1], control_wires=[0])
+
+    def test_deprecated_base_already_has_wires(self):
+        """Test initialization with invalid control values"""
+        U = np.array([[0, 1], [1, 0]])
+        U = qml.QubitUnitary(U, wires=0)
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="The control_wires input to ControlledQubitUnitary is deprecated",
+        ):
+            with pytest.warns(
+                qml.PennyLaneDeprecationWarning,
+                match="QubitUnitary input to ControlledQubitUnitary is deprecated",
+            ):
+                qml.ControlledQubitUnitary(
+                    U, control_wires=[2, 3, 4], wires=[1], control_values=[1, 0, 1]
+                )
+
+    def test_deprecated_control_wires_argument_error(self):
+        """Test initialization with deprecated control_wires argument"""
+        U = np.array([[0, 1], [1, 0]])
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="The control_wires input to ControlledQubitUnitary is deprecated",
+        ):
+            with pytest.raises(TypeError, match="Must specify a set of wires"):
+                qml.ControlledQubitUnitary(U, control_wires=[])
+
+    @pytest.mark.parametrize("base", (qml.X(0), qml.PauliX(0) @ qml.RX(0.5, wires=1)))
+    def test_deprecated_interface_still_available(self, base):
+        """Test that the deprecated interface is still available"""
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="The control_wires input to ControlledQubitUnitary is deprecated",
+        ):
+            with pytest.warns(
+                qml.PennyLaneDeprecationWarning,
+                match="QubitUnitary input to ControlledQubitUnitary is deprecated",
+            ):
+                qml.ControlledQubitUnitary(base, control_wires=[1, 2, 3])
+
+    def test_wires_is_none(self):
+        """Test that an error is raised if the user provides no target wires for an iterable base operator"""
+        base_op = [[0, 1], [1, 0]]
+
+        with pytest.raises(TypeError, match="Must specify a set of wires"):
+            qml.ControlledQubitUnitary(base_op, wires=None)
 
     @pytest.mark.jax
     @pytest.mark.usefixtures("enable_disable_plxpr")
@@ -81,10 +150,10 @@ class TestControlledQubitUnitary:
         """Test that the operator wires are as expected with capture enabled"""
         base_op = [[0, 1], [1, 0]]
 
-        op_kwarg = qml.ControlledQubitUnitary(base_op, control_wires=control_wires, wires=wires)
+        op_kwarg = qml.ControlledQubitUnitary(base_op, wires=Wires(control_wires) + Wires(wires))
         assert op_kwarg.base.wires == Wires(wires)
         assert op_kwarg.control_wires == Wires(control_wires)
-        op = qml.ControlledQubitUnitary(base_op, control_wires, wires)
+        op = qml.ControlledQubitUnitary(base_op, wires=Wires(control_wires) + Wires(wires))
         assert op.base.wires == Wires(wires)
         assert op.control_wires == Wires(control_wires)
 
@@ -95,52 +164,27 @@ class TestControlledQubitUnitary:
         base_op = [[0, 1], [1, 0]]
 
         control_wires_1, wires_1 = [0, 1], [2]
-        op_1 = qml.ControlledQubitUnitary(base_op, control_wires=control_wires_1, wires=wires_1)
+        op_1 = qml.ControlledQubitUnitary(base_op, wires=control_wires_1 + wires_1)
 
         assert op_1.base.wires == Wires(2)
         assert op_1.control_wires == Wires([0, 1])
 
         control_wires_2, wires_2 = [0, 1, 2], ()
-        op_2 = qml.ControlledQubitUnitary(base_op, control_wires=control_wires_2, wires=wires_2)
+        op_2 = qml.ControlledQubitUnitary(base_op, wires=Wires(control_wires_2) + Wires(wires_2))
 
         assert op_2.base.wires == Wires(2)
         assert op_2.control_wires == Wires([0, 1])
 
     def test_initialization_from_matrix_and_operator(self):
         base_op = QubitUnitary(X, wires=1)
-
-        op1 = qml.ControlledQubitUnitary(X, control_wires=[0, 2], wires=1)
-        op2 = qml.ControlledQubitUnitary(base_op, control_wires=[0, 2])
-
-        qml.assert_equal(op1, op2)
-
-    def test_no_control(self):
-        """Test if ControlledQubitUnitary raises an error if control wires are not specified"""
-        with pytest.raises(
-            TypeError, match="missing 1 required positional argument: 'control_wires'"
-        ):
-            qml.ControlledQubitUnitary(X, wires=2)
-
-    def test_shared_control(self):
-        """Test if ControlledQubitUnitary raises an error if control wires are shared with wires"""
-        with pytest.raises(
-            ValueError, match="The control wires must be different from the base operation wires"
-        ):
-            qml.ControlledQubitUnitary(X, control_wires=[0, 2], wires=2)
-
-    def test_wires_specified_twice_warning(self):
-        base = qml.QubitUnitary(X, 0)
         with pytest.warns(
-            UserWarning,
-            match="base operator already has wires; values specified through wires kwarg will be ignored.",
+            qml.PennyLaneDeprecationWarning,
+            match="QubitUnitary input to ControlledQubitUnitary is deprecated",
         ):
-            qml.ControlledQubitUnitary(base, control_wires=[1, 2], wires=3)
+            op1 = qml.ControlledQubitUnitary(X, wires=[0, 2, 1])
+            op2 = qml.ControlledQubitUnitary(base_op, wires=[0, 2, 1])
 
-    def test_wrong_shape(self):
-        """Test if ControlledQubitUnitary raises a ValueError if a unitary of shape inconsistent
-        with wires is provided"""
-        with pytest.raises(ValueError, match=r"Input unitary must be of shape \(2, 2\)"):
-            qml.ControlledQubitUnitary(np.eye(4), control_wires=[0, 1], wires=2).matrix()
+            qml.assert_equal(op1, op2)
 
     @pytest.mark.parametrize("target_wire", list(range(3)))
     def test_toffoli(self, target_wire):
@@ -158,7 +202,7 @@ class TestControlledQubitUnitary:
         @qml.qnode(dev)
         def f1():
             qml.QubitUnitary(U1, wires=range(3))
-            qml.ControlledQubitUnitary(X, control_wires=control_wires, wires=target_wire)
+            qml.ControlledQubitUnitary(X, wires=control_wires + [target_wire])
             qml.QubitUnitary(U2, wires=range(3))
             return qml.state()
 
@@ -190,9 +234,7 @@ class TestControlledQubitUnitary:
         @qml.qnode(dev)
         def f1():
             qml.QubitUnitary(U1, wires=range(3))
-            qml.ControlledQubitUnitary(
-                X_broadcasted, control_wires=control_wires, wires=target_wire
-            )
+            qml.ControlledQubitUnitary(X_broadcasted, wires=control_wires + [target_wire])
             qml.QubitUnitary(U2, wires=range(3))
             return qml.state()
 
@@ -247,7 +289,7 @@ class TestControlledQubitUnitary:
         @qml.qnode(dev)
         def f1():
             qml.QubitUnitary(U1, wires=range(4))
-            qml.ControlledQubitUnitary(U, control_wires=control_wires, wires=target_wires)
+            qml.ControlledQubitUnitary(U, wires=control_wires + target_wires)
             qml.QubitUnitary(U2, wires=range(4))
             return qml.state()
 
@@ -275,7 +317,7 @@ class TestControlledQubitUnitary:
             ValueError, match="control_values should be the same length as control_wires"
         ):
             qml.ControlledQubitUnitary(
-                X, control_wires=control_wires, wires=target_wires, control_values=control_values
+                X, wires=control_wires + target_wires, control_values=control_values
             )
 
     @pytest.mark.parametrize(
@@ -312,7 +354,7 @@ class TestControlledQubitUnitary:
             qml.templates.ArbitraryStatePreparation(target_state_weights, wires=target_wires)
 
             qml.ControlledQubitUnitary(
-                U, control_wires=control_wires, wires=target_wires, control_values=control_values
+                U, wires=control_wires + target_wires, control_values=control_values
             )
             return qml.state()
 
@@ -330,7 +372,7 @@ class TestControlledQubitUnitary:
             for wire in x_locations:
                 qml.PauliX(wires=control_wires[wire])
 
-            qml.ControlledQubitUnitary(U, control_wires=control_wires, wires=wires)
+            qml.ControlledQubitUnitary(U, wires=control_wires + target_wires)
 
             for wire in x_locations:
                 qml.PauliX(wires=control_wires[wire])
@@ -345,14 +387,14 @@ class TestControlledQubitUnitary:
     def test_same_as_Toffoli(self):
         """Test if ControlledQubitUnitary returns the correct matrix for a control-control-X
         (Toffoli) gate"""
-        mat = qml.ControlledQubitUnitary(X, control_wires=[0, 1], wires=2).matrix()
+        mat = qml.ControlledQubitUnitary(X, wires=[0, 1, 2]).matrix()
         mat2 = qml.Toffoli(wires=[0, 1, 2]).matrix()
         assert np.allclose(mat, mat2)
 
     def test_matrix_representation(self, tol):
         """Test that the matrix representation is defined correctly"""
         U = np.array([[0.94877869, 0.31594146], [-0.31594146, 0.94877869]])
-        res_dynamic = qml.ControlledQubitUnitary(U, control_wires=[1], wires=0).matrix()
+        res_dynamic = qml.ControlledQubitUnitary(U, wires=[1, 0]).matrix()
         expected = np.array(
             [
                 [1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j],
@@ -373,7 +415,7 @@ class TestControlledQubitUnitary:
             ]
         )
 
-        res_dynamic = qml.ControlledQubitUnitary(U, control_wires=[1], wires=0).matrix()
+        res_dynamic = qml.ControlledQubitUnitary(U, wires=[1, 0]).matrix()
         expected = np.array(
             [
                 [
@@ -408,9 +450,7 @@ class TestControlledQubitUnitary:
             ]
         )
 
-        op = qml.ControlledQubitUnitary(
-            U1, control_wires=("b", "c"), wires="a", control_values="01"
-        )
+        op = qml.ControlledQubitUnitary(U1, wires=("b", "c", "a"), control_values="01")
 
         pow_ops = op.pow(n)
         assert len(pow_ops) == 1
@@ -437,7 +477,7 @@ class TestControlledQubitUnitary:
             axes=0,
         )
 
-        op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a")
+        op = qml.ControlledQubitUnitary(U1, wires=("b", "c", "a"))
 
         pow_ops = op.pow(n)
         assert len(pow_ops) == 1
@@ -457,7 +497,7 @@ class TestControlledQubitUnitary:
             ]
         )
 
-        op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a")
+        op = qml.ControlledQubitUnitary(U1, wires=("b", "c", "a"))
 
         z = 0.12
         [pow_op] = op.pow(z)
@@ -478,7 +518,7 @@ class TestControlledQubitUnitary:
             axes=0,
         )
 
-        op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a")
+        op = qml.ControlledQubitUnitary(U1, wires=("b", "c", "a"))
 
         with pytest.raises(qml.operation.PowUndefinedError):
             op.pow(0.12)
@@ -488,10 +528,8 @@ class TestControlledQubitUnitary:
 
         U = qml.PauliX(0).compute_matrix()
 
-        original = qml.ControlledQubitUnitary(U, control_wires=(0, 1), wires=4, control_values="01")
-        expected = qml.ControlledQubitUnitary(
-            U, control_wires=(0, 1, "a"), wires=4, control_values="011"
-        )
+        original = qml.ControlledQubitUnitary(U, wires=(0, 1, 4), control_values="01")
+        expected = qml.ControlledQubitUnitary(U, wires=(0, 1, "a", 4), control_values="011")
 
         out = original._controlled("a")  # pylint: disable=protected-access
         qml.assert_equal(out, expected)
@@ -500,12 +538,10 @@ class TestControlledQubitUnitary:
         unitary = np.array([[0.94877869j, 0.31594146], [-0.31594146, 0.94877869j]])
         not_unitary = np.array([[0.94877869j, 0.31594146], [-5, 0.94877869j]])
 
-        qml.ControlledQubitUnitary(unitary, control_wires=[0, 2], wires=1, unitary_check=True)
+        qml.ControlledQubitUnitary(unitary, wires=[0, 2, 1], unitary_check=True)
 
         with pytest.warns(UserWarning, match="may not be unitary"):
-            qml.ControlledQubitUnitary(
-                not_unitary, control_wires=[0, 2], wires=1, unitary_check=True
-            )
+            qml.ControlledQubitUnitary(not_unitary, wires=[0, 2, 1], unitary_check=True)
 
 
 @pytest.mark.parametrize("op_cls, _", NON_PARAMETRIZED_OPERATIONS)
diff --git a/tests/ops/qubit/test_attributes.py b/tests/ops/qubit/test_attributes.py
index a9bf5d3d4..244892582 100644
--- a/tests/ops/qubit/test_attributes.py
+++ b/tests/ops/qubit/test_attributes.py
@@ -283,15 +283,14 @@ class TestSupportsBroadcasting:
         actually does support broadcasting."""
 
         U = np.array([unitary_group.rvs(4, random_state=state) for state in [91, 1, 4]])
-        wires = [0, "9"]
+        target_wires = [0, "9"]
+        control_wires = [1, "10"]
+        wires = control_wires + target_wires
 
-        op = qml.ControlledQubitUnitary(U, wires=wires, control_wires=[1, "10"])
+        op = qml.ControlledQubitUnitary(U, wires=wires)
 
         mat1 = op.matrix()
-        single_mats = [
-            qml.ControlledQubitUnitary(_U, wires=wires, control_wires=[1, "10"]).matrix()
-            for _U in U
-        ]
+        single_mats = [qml.ControlledQubitUnitary(_U, wires=wires).matrix() for _U in U]
 
         assert qml.math.allclose(mat1, single_mats)
 
diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py
index c432eacf2..0348bf734 100644
--- a/tests/ops/qubit/test_matrix_ops.py
+++ b/tests/ops/qubit/test_matrix_ops.py
@@ -360,7 +360,7 @@ class TestQubitUnitary:
         U = qml.PauliX.compute_matrix()
         base = qml.QubitUnitary(U, wires=0)
 
-        expected = qml.ControlledQubitUnitary(U, control_wires="a", wires=0)
+        expected = qml.ControlledQubitUnitary(U, wires=["a", 0])
 
         out = base._controlled("a")
         qml.assert_equal(out, expected)
@@ -735,7 +735,7 @@ class TestDiagonalQubitUnitary:
 labels = [X, X, [1, 1]]
 ops = [
     qml.QubitUnitary(X, wires=0),
-    qml.ControlledQubitUnitary(X, control_wires=0, wires=1),
+    qml.ControlledQubitUnitary(X, wires=[0, 1]),
     qml.DiagonalQubitUnitary([1, 1], wires=0),
 ]
 
@@ -1179,7 +1179,7 @@ class TestInterfaceMatricesLabel:
 control_data = [
     (qml.QubitUnitary(X, wires=0), Wires([])),
     (qml.DiagonalQubitUnitary([1, 1], wires=1), Wires([])),
-    (qml.ControlledQubitUnitary(X, control_wires=0, wires=1), Wires([0])),
+    (qml.ControlledQubitUnitary(X, wires=[0, 1]), Wires([0])),
 ]
 
 
diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py
index 453b7c408..35526fb00 100644
--- a/tests/ops/qubit/test_non_parametric_ops.py
+++ b/tests/ops/qubit/test_non_parametric_ops.py
@@ -695,7 +695,7 @@ class TestMultiControlledX:
             for wire in x_locations:
                 qml.PauliX(wires=control_wires[wire])
 
-            qml.ControlledQubitUnitary(X, control_wires=control_wires, wires=target_wires)
+            qml.ControlledQubitUnitary(X, wires=control_wires + target_wires)
 
             for wire in x_locations:
                 qml.PauliX(wires=control_wires[wire])
diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py
index f5c29ea4d..413eea086 100644
--- a/tests/ops/qubit/test_parametric_ops.py
+++ b/tests/ops/qubit/test_parametric_ops.py
@@ -56,7 +56,7 @@ PARAMETRIZED_OPERATIONS = [
     qml.CRot(0.123, 0.456, 0.789, wires=[0, 1]),
     qml.QubitUnitary(np.eye(2) * 1j, wires=0),
     qml.DiagonalQubitUnitary(np.array([1.0, 1.0j]), wires=1),
-    qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[0], control_wires=[2]),
+    qml.ControlledQubitUnitary(np.eye(2) * 1j, wires=[2, 0]),
     qml.SingleExcitation(0.123, wires=[0, 3]),
     qml.SingleExcitationPlus(0.123, wires=[0, 3]),
     qml.SingleExcitationMinus(0.123, wires=[0, 3]),
diff --git a/tests/transforms/test_optimization/test_commute_controlled.py b/tests/transforms/test_optimization/test_commute_controlled.py
index ad3c60495..84708e344 100644
--- a/tests/transforms/test_optimization/test_commute_controlled.py
+++ b/tests/transforms/test_optimization/test_commute_controlled.py
@@ -46,7 +46,7 @@ class TestCommuteControlled:
 
         def qfunc():
             qml.PauliX(wires=2)
-            qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), control_wires=0, wires=2)
+            qml.ControlledQubitUnitary(np.array([[0, 1], [1, 0]]), wires=[0, 2])
             qml.PauliX(wires=2)
 
         transformed_qfunc = commute_controlled(qfunc, direction=direction)
diff --git a/tests/transforms/test_qmc_transform.py b/tests/transforms/test_qmc_transform.py
index 1a75a62c7..8c399fb9b 100644
--- a/tests/transforms/test_qmc_transform.py
+++ b/tests/transforms/test_qmc_transform.py
@@ -109,7 +109,7 @@ def test_apply_controlled_z(n_wires):
     # because two Zs are used.
     z_ideal = -_make_Z(2**n_wires)
 
-    circ = lambda: qml.ControlledQubitUnitary(z_ideal, wires=wires, control_wires=control_wire)
+    circ = lambda: qml.ControlledQubitUnitary(z_ideal, wires=control_wire + wires)
     u_ideal = get_unitary(circ, n_all_wires)
 
     assert np.allclose(u, u_ideal)
@@ -131,7 +131,7 @@ def test_apply_controlled_v(n_wires):
     # because two Vs are used.
     v_ideal = -_make_V(2**n_wires)
 
-    circ = lambda: qml.ControlledQubitUnitary(v_ideal, wires=wires, control_wires=control_wire)
+    circ = lambda: qml.ControlledQubitUnitary(v_ideal, wires=control_wire + wires)
     u_ideal = get_unitary(circ, n_all_wires)
 
     assert np.allclose(u, u_ideal)
@@ -166,7 +166,7 @@ class TestApplyControlledQ:
 
         u = get_unitary(circ, n_all_wires)
 
-        circ = lambda: qml.ControlledQubitUnitary(q_mat, wires=wires, control_wires=control_wire)
+        circ = lambda: qml.ControlledQubitUnitary(q_mat, wires=Wires(control_wire) + Wires(wires))
         u_ideal = get_unitary(circ, n_all_wires)
 
         assert np.allclose(u_ideal, u)