|
| 1 | +- Start Date: (fill in with date at which the RFC is merged, YYYY-MM-DD) |
| 2 | +- RFC PR: [amaranth-lang/rfcs#66](https://github.com/amaranth-lang/rfcs/pull/66) |
| 3 | +- Amaranth Issue: [amaranth-lang/amaranth#0000](https://github.com/amaranth-lang/amaranth/issues/0000) |
| 4 | + |
| 5 | +# Simulation time |
| 6 | + |
| 7 | +## Summary |
| 8 | +[summary]: #summary |
| 9 | + |
| 10 | +Add a type for representing time durations and simulator methods to query the elapsed time. |
| 11 | + |
| 12 | +## Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +In [RFC #36](0036-async-testbench-functions.md), 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. |
| 16 | + |
| 17 | +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. |
| 18 | + |
| 19 | +Also, to quote @whitequark: Time is not a number. |
| 20 | + |
| 21 | +## Guide- and reference-level explanation |
| 22 | +[guide-level-explanation]: #guide-level-explanation |
| 23 | + |
| 24 | +A new type `amaranth.sim.Period` is introduced. |
| 25 | +It is immutable, has no public constructor and is instead constructed through the following classmethods: |
| 26 | +- `.s(duration: numbers.Real)` |
| 27 | +- `.ms(duration: numbers.Real)` |
| 28 | +- `.us(duration: numbers.Real)` |
| 29 | +- `.ns(duration: numbers.Real)` |
| 30 | +- `.ps(duration: numbers.Real)` |
| 31 | +- `.fs(duration: numbers.Real)` |
| 32 | +- `.Hz(frequency: numbers.Real)` |
| 33 | +- `.kHz(frequency: numbers.Real)` |
| 34 | +- `.MHz(frequency: numbers.Real)` |
| 35 | +- `.GHz(frequency: numbers.Real)` |
| 36 | + - The argument will be scaled according to the SI prefix on the method and the `duration` or reciprocal of `frequency` then used to calculate the closest integer femtosecond representation. |
| 37 | + |
| 38 | +To convert it back to a number, the following properties are available: |
| 39 | +- `.seconds -> fractions.Fraction` |
| 40 | +- `.milliseconds -> fractions.Fraction` |
| 41 | +- `.microseconds -> fractions.Fraction` |
| 42 | +- `.nanoseconds -> fractions.Fraction` |
| 43 | +- `.picoseconds -> fractions.Fraction` |
| 44 | +- `.femtoseconds -> int` |
| 45 | + |
| 46 | +The following operators are defined: |
| 47 | +- `.__lt__(other: Period) -> bool` |
| 48 | +- `.__le__(other: Period) -> bool` |
| 49 | +- `.__eq__(other: Period) -> bool` |
| 50 | +- `.__ne__(other: Period) -> bool` |
| 51 | +- `.__gt__(other: Period) -> bool` |
| 52 | +- `.__ge__(other: Period) -> bool` |
| 53 | +- `.__hash__() -> int` |
| 54 | +- `.__bool__() -> bool` |
| 55 | +- `.__neg__() -> Period` |
| 56 | +- `.__pos__() -> Period` |
| 57 | +- `.__abs__() -> Period` |
| 58 | +- `.__add__(other: Period) -> Period` |
| 59 | +- `.__sub__(other: Period) -> Period` |
| 60 | +- `.__mul__(other: numbers.Real) -> Period` |
| 61 | +- `.__rmul__(other: numbers.Real) -> Period` |
| 62 | +- `.__truediv__(other: numbers.Real) -> Period` |
| 63 | +- `.__truediv__(other: Period) -> fractions.Fraction` |
| 64 | +- `.__floordiv__(other: Period) -> int` |
| 65 | +- `.__mod__(other: Period) -> Period` |
| 66 | + - Operators on `Period`s are performed on the underlying femtosecond values. |
| 67 | + - Operators involving `numbers.Real` operands have the result rounded back to the closest integer femtosecond representation. |
| 68 | + - Operators given unsupported operand combinations will return `NotImplemented`. |
| 69 | + |
| 70 | +`Simulator` and `SimulatorContext` both have a `.time() -> Period` method added that returns the elapsed time since start of simulation. |
| 71 | + |
| 72 | +The methods that currently take seconds as a `float` are updated to take a `Period`: |
| 73 | +- `.add_clock()` |
| 74 | +- `.delay()` |
| 75 | + |
| 76 | +The ability to pass seconds as a `float` is deprecated and removed in a future Amaranth version. |
| 77 | + |
| 78 | +## Drawbacks |
| 79 | +[drawbacks]: #drawbacks |
| 80 | + |
| 81 | +- Deprecating being able to pass seconds as a `float` creates churn. |
| 82 | + |
| 83 | +## Rationale and alternatives |
| 84 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 85 | + |
| 86 | +- `.add_clock()` is usually passed the reciprocal of a frequency, so being able to construct a `Period` from a frequency instead of a duration is useful. |
| 87 | + - We could add a separate `Frequency` type, but in practice it would likely almost exclusively be used to calculate the reciprocal `Period`. |
| 88 | + |
| 89 | +- Accepting a `numbers.Real` when we're converting from a number lets us pass in any standard suitable number type. |
| 90 | + |
| 91 | +- When converting to a number, `fractions.Fraction` is a standard `numbers.Real` type that is guaranteed to represent any possible value exactly, and is convertible to `int` or `float`. |
| 92 | + - Returning `int` would force a potentially undesirable truncation/rounding upon the user. |
| 93 | + - Returning `float` would lose precision when the number of femtoseconds are larger than the significand. |
| 94 | + - `decimal.Decimal` is decimal floating point and thus has the same issue as `float`. |
| 95 | + - `.femtoseconds` returns `int` because that's a perfect representation and returning a fraction with a denominator of 1 adds no value. |
| 96 | + |
| 97 | +- The supported set of operators are the ones that have meaningful and useful semantics: |
| 98 | + - Comparing, adding and subtracting time periods makes sense. |
| 99 | + - Multiplying a time period with or dividing it by a real number scales the time period. |
| 100 | + - Dividing time periods gives the ratio between them. |
| 101 | + - Modulo of time periods is the remainder and thus still a time period. |
| 102 | + Potentially useful to calculate the phase offset between a timestamp and a clock period. |
| 103 | + - Reflected operators that only accept `Period` operands are redundant and omitted. |
| 104 | + |
| 105 | +## Prior art |
| 106 | +[prior-art]: #prior-art |
| 107 | + |
| 108 | +None. |
| 109 | + |
| 110 | +## Unresolved questions |
| 111 | +[unresolved-questions]: #unresolved-questions |
| 112 | + |
| 113 | +- The usual bikeshedding. |
| 114 | + - Case, e.g. `.MHz` vs `.mhz`. |
| 115 | + |
| 116 | +## Future possibilities |
| 117 | +[future-possibilities]: #future-possibilities |
| 118 | + |
| 119 | +None. |
0 commit comments