- Start Date: 2024-07-29
- RFC PR: amaranth-lang/rfcs#66
- Amaranth Issue: amaranth-lang/amaranth#0000
Simulation time
Summary
Add a type for representing time durations (including clock periods) and simulator methods to query the elapsed time.
Motivation
In RFC #36, there were a plan to introduce a .time() method to query the current simulation time, but it got deferred due to the lack of a suitable return type.
Internally simulation time is an integer number of femtoseconds, but exposing that directly is not ergonomic and making it a float of seconds sacrifices precision the longer a simulation runs.
Also, to quote @whitequark: Time is not a number.
Guide- and reference-level explanation
A new type Period is introduced.
It is exported from amaranth.hdl and also re-exported from amaranth.sim.
It is immutable and has a constructor accepting at most one named argument, giving the following valid forms of construction:
Period()- Constructs a zero period.
Period(s: numbers.Real)Period(ms: numbers.Real)Period(us: numbers.Real)Period(ns: numbers.Real)Period(ps: numbers.Real)Period(fs: numbers.Real)- The argument will be scaled according to its SI prefix and used to calculate the closest integer femtosecond representation.
Period(Hz: numbers.Real)Period(kHz: numbers.Real)Period(MHz: numbers.Real)Period(GHz: numbers.Real)- The argument will be scaled according to its SI prefix and its reciprocal used to calculate the closest integer femtosecond representation.
- A value of zero will raise
ZeroDivisionError. - A negative value will raise
ValueError.
To convert it back to a number, the following properties are available:
.seconds -> float.milliseconds -> float.microseconds -> float.nanoseconds -> float.picoseconds -> float.femtoseconds -> int
To calculate the reciprocal frequency, the following properties are available:
.hertz -> float.kilohertz -> float.megahertz -> float.gigahertz -> float- Accessing these properties when the period is zero will raise
ZeroDivisionError. - Accessing these properties when the period is negative will raise
ValueError.
- Accessing these properties when the period is zero will raise
The following operators are defined:
.__lt__(other: Period) -> bool.__le__(other: Period) -> bool.__eq__(other: Period) -> bool.__ne__(other: Period) -> bool.__gt__(other: Period) -> bool.__ge__(other: Period) -> bool.__hash__() -> int.__bool__() -> bool.__neg__() -> Period.__pos__() -> Period.__abs__() -> Period.__add__(other: Period) -> Period.__sub__(other: Period) -> Period.__mul__(other: numbers.Real) -> Period.__rmul__(other: numbers.Real) -> Period.__truediv__(other: numbers.Real) -> Period.__truediv__(other: Period) -> float.__floordiv__(other: Period) -> int.__mod__(other: Period) -> Period- Operators on
Periods are performed on the underlying femtosecond values. - Operators involving
numbers.Realoperands have the result rounded back to the closest integer femtosecond representation. - Operators given unsupported operand combinations will return
NotImplemented.
- Operators on
.__str__() -> str- Equivalent to
.__format__("")
- Equivalent to
.__format__(format_spec: str) -> str- The format specifier format is
[width][.precision][ ][unit]. - An invalid format specifier raises
ValueError. - If
widthis specified, the string is left-padded with space to at least the requested width. - If
precisionis specified, the requested number of decimal digits will be emitted. Otherwise, duration units will emit as many digits required for an exact result, while frequency units will defer to defaultfloatformatting. - If a space is present in the format specifier, the formatted string will have a space between the number and the unit.
unitcan be specified as any of the argument names accepted by the constructor. If a unit is not specified, the largest duration unit that has a nonzero integer part is used. Formatting frequency units have the same restrictions and exception behavior as accessing frequency properties.
- The format specifier format is
SimulatorContext have an .elapsed_time() -> Period method added that returns the elapsed time since start of simulation.
These methods that has a period argument currently taking seconds as a float are updated to take a Period:
Simulator.add_clock()SimulatorContext.delay()
These methods that has a frequency argument currently taking Hz as a float are updated to take a Period:
ResourceManager.add_clock_constraint()Clock.__init__()
The ability to pass seconds or Hz directly as a float is deprecated and removed in a future Amaranth version.
The Clock.period property is updated to return a Period and the Clock.frequency property is deprecated.
Consequently, Platform.default_clk_frequency() is also deprecated and replaced with a new method Platform.default_clk_period() -> Period.
Drawbacks
- Deprecating being able to pass seconds or Hz directly as a
floatcreates churn.
Rationale and alternatives
-
.add_clock()is usually passed the reciprocal of a frequency, so being able to construct aPeriodfrom a frequency instead of a duration is useful.- We could add a separate
Frequencytype, but in practice it would likely almost exclusively be used to calculate the reciprocalPeriod.
- We could add a separate
-
Accepting a
numbers.Realwhen we're converting from a number lets us pass in any standard suitable number type. -
The supported set of operators are the ones that have meaningful and useful semantics:
- Comparing, adding and subtracting time periods makes sense.
- Multiplying a time period with or dividing it by a real number scales the time period.
- Dividing time periods gives the ratio between them.
- Modulo of time periods is the remainder and thus still a time period. Potentially useful to calculate the phase offset between a timestamp and a clock period.
- Reflected operators that only accept
Periodoperands are redundant and omitted.
-
.elapsed_time()is only available from within a testbench/process, where it is well defined. -
Instead of named constructor arguments, we could use a classmethod for each SI prefix. This was proposed in an earlier revision of this RFC.
-
Instead of returning
float, we could returnfractions.Fractionto ensure no precision loss when the number of femtoseconds is larger than thefloatsignificand. This was proposed in an earlier revision of this RFC.- Rounding to integer femtoseconds is already lossy and a further precision loss from converting to a
floatis negligible in most real world use cases. For any applications requiring an exact conversion of aPeriod, the.femtosecondsproperty is always exact.
- Rounding to integer femtoseconds is already lossy and a further precision loss from converting to a
Prior art
None.
Unresolved questions
None.
Future possibilities
None.