Repository: cocotb/cocotb

Issue:
Timer value argument is misleading 
With the existence of the ``units`` argument, the value in ``Timer`` should not be ``time_ps`` (https://cocotb.readthedocs.io/en/latest/triggers.html?highlight=Timer#cocotb.triggers.Timer). 

How about ``value``? 


Discussion:
Regarding this and #1117. 

I don't even think the default units are picoseconds, but the simulator's timescale, which can change via flags to the simulator. Not using `units` is probably a bug/bug waiting to happen. Maybe a `DeprecationWarning` is appropriate.
Maybe we should even take the step and create ``SimTime`` as @imphil suggested in #872. 
I'd like something like `std::chrono::duration` for expressing sim time, I've run into the need for such a type on a few occassions.
@garmin-mjames What is the request for 1.5 here? The rename, or the simulation time physical type I described?
A rename was my goal for 1.5.
Supporting a physical `time` type in Python would help #1233 as well.
https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias could be useful here
The first part of the first solution would not be good for Sphinx, but the second part would work fine. I would introduce two special objects that would be helpful for showing the intended use.

```python
DEPRECATED = object()
REQUIRED = object()

def function(new_name=REQUIRED, *, old_name=DEPRECATED):
    # ... logic from answer 1 part 2 just replacing `None`, `None` might be a possible argument anyway, so we need a better sentinel
```

Resolving patch:
diff --git a/cocotb/triggers.py b/cocotb/triggers.py
--- a/cocotb/triggers.py
+++ b/cocotb/triggers.py
@@ -158,12 +158,14 @@ def unprime(self):
 class Timer(GPITrigger):
     """Fires after the specified simulation time period has elapsed."""
 
-    def __init__(self, time_ps, units="step"):
+    def __init__(self, time=None, units="step", *, time_ps=None):
         """
         Args:
-           time_ps (numbers.Real or decimal.Decimal): The time value.
+           time (numbers.Real or decimal.Decimal): The time value.
                Note that despite the name this is not actually in picoseconds
                but depends on the *units* argument.
+               .. versionchanged:: 1.5.0
+                  Previously this argument was misleadingly called `time_ps`.
            units (str, optional): One of
                ``'step'``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``.
                When *units* is ``'step'``,
@@ -210,8 +212,18 @@ def __init__(self, time_ps, units="step"):
             Using None as the the *units* argument is deprecated, use ``'step'`` instead.
         """
         GPITrigger.__init__(self)
-        if time_ps <= 0:
-            if time_ps == 0:
+        if time_ps is not None:
+            if time is not None:
+                raise TypeError("Gave argument to both the 'time' and deprecated 'time_ps' parameter")
+            time = time_ps
+            warnings.warn(
+                "The parameter name 'time_ps' has been renamed to 'time'. Please update your invocation.",
+                DeprecationWarning, stacklevel=2)
+        else:
+            if time is None:
+                raise TypeError("Missing required argument 'time'")
+        if time <= 0:
+            if time == 0:
                 warnings.warn("Timer setup with value 0, which might exhibit undefined behavior in some simulators",
                               category=RuntimeWarning,
                               stacklevel=2)
@@ -221,8 +233,8 @@ def __init__(self, time_ps, units="step"):
             warnings.warn(
                 'Using units=None is deprecated, use units="step" instead.',
                 DeprecationWarning, stacklevel=2)
-            units="step"  # don't propagate deprecated value
-        self.sim_steps = get_sim_steps(time_ps, units)
+            units = "step"  # don't propagate deprecated value
+        self.sim_steps = get_sim_steps(time, units)
 
     def prime(self, callback):
         """Register for a timed callback."""


Test patch:
diff --git a/tests/test_cases/test_cocotb/test_deprecated.py b/tests/test_cases/test_cocotb/test_deprecated.py
--- a/tests/test_cases/test_cocotb/test_deprecated.py
+++ b/tests/test_cases/test_cocotb/test_deprecated.py
@@ -138,3 +138,13 @@ async def t():
         cocotb.test(expect_error=True)(t)
     with assert_deprecated():
         cocotb.test(expect_error=False)(t)
+
+
+@cocotb.test()
+async def test_time_ps_deprecated(_):
+    with assert_deprecated():
+        Timer(time_ps=7, units='ns')
+    with assert_raises(TypeError):
+        Timer(time=0, time_ps=7, units='ns')
+    with assert_raises(TypeError):
+        Timer(units='ps')