Skip to main content

gwr_engine/time/
mod.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3/*!
4Modules that model time within the simulations.
5
6<!-- ANCHOR: clock_overview -->
7
8Clocks are used to control time within a GWR simulation. The [engine] supports any
9number of clocks running at different frequencies.
10
11Each clock represents a clock domain. This lets one simulation contain blocks
12that operate at different frequencies without converting everything into one
13global tick rate. For example, a platform can model a 1GHz block and a 2GHz
14block with separate clocks; one tick on the 1GHz clock and two ticks on the 2GHz
15clock both represent `1.0ns`, but each clock domain keeps its own local tick
16count.
17
18The engine schedules clock waits by absolute simulation time and then by clock
19phase. Tasks remain event-driven: they only wake when their awaited event, port,
20or clock wait is ready.
21
22If two clock ticks resolve to exactly the same time and phase the [engine] does
23not provide any guarantees about which events will be evaluated first.
24
25## Default Clock
26
27The [engine] is responsible for managing clocks. Use the default clock when the
28frequency does not matter (the default is currently 1GHz, but that may change):
29
30```rust,no_run
31# use gwr_engine::engine::Engine;
32# fn main() {
33let mut engine = Engine::default();
34let clock = engine.default_clock();
35# }
36```
37
38## Creating a Clock
39
40When a well-defined clock frequency is required, create clocks explicitly.
41A non-default clock runs at a user-specified frequency and can be created
42with the [engine]'s helper functions.
43
44The following two clocks are equivalent:
45
46```rust,no_run
47# use gwr_engine::engine::Engine;
48# fn main() {
49let mut engine = Engine::default();
50let clock_a = engine.clock_ghz(1.0);
51let clock_b = engine.clock_mhz(1000.0);
52# }
53```
54
55## Advancing Time
56
57Time is advanced by waiting an integer number of ticks on a clock. In the
58snippet below the `println!` will be called when the time has advanced to
59`1.0ns`.
60
61```rust,no_run
62# use gwr_engine::engine::Engine;
63# fn main() {
64# let mut engine = Engine::default();
65# let spawner = engine.spawner();
66let clock = engine.clock_ghz(1.0);
67# spawner.spawn(async move {
68clock.wait_ticks(1).await;
69println!("Time now {:.2}", clock.time_now_ns());
70# Ok(())
71#  });
72# }
73```
74
75## Clock Phases
76
77Clock time is represented using the `ClockTick` type, made up of a tick count and a phase
78within that tick. Phases provide deterministic ordering inside a tick.
79
80The two standard phases are:
81
82- `phase::BEGIN`: the start of a tick. `wait_ticks(...)` waits resume in this
83  phase.
84- `phase::END`: the end of a tick. Components can wait for this phase when they
85  need all same-tick releases or bookkeeping to happen before starting new
86  activity.
87
88Custom `u32` phase values can be used between `phase::BEGIN` and `phase::END`
89when a model needs additional deterministic ordering points. A task can use
90`wait_phase(phase)` to move later within the current tick, or
91`next_tick_and_phase(phase)` to wait until a specific phase in the next tick.
92
93For example, certain models use `phase::END` before starting new activities
94in order to guarantee that any completing activities have been processed first:
95
96```rust,no_run
97# use gwr_engine::engine::Engine;
98# use gwr_engine::time::clock::phase;
99# fn main() {
100# let mut engine = Engine::default();
101let clock = engine.clock_ghz(1.0);
102# let spawner = engine.spawner();
103# spawner.spawn(async move {
104clock.wait_phase(phase::END).await;
105// Begin activity that should be ordered after same-tick completions.
106# Ok(())
107# });
108# }
109```
110
111## Background Tasks
112
113By default a simulation will run until all events have completed. However,
114sometimes it is useful to create a monitor task like a progress bar that just
115needs to run as long as the rest of the simulation.
116
117In order to do this the `wait_ticks_or_exit` function can be called. This lets
118the [engine] know that it does not have to keep running if this is the only thread
119of activity left. For example, the code below will start a thread of activity
120that prints the current time in `ns` periodically as long as the simulation is
121running:
122
123```rust,no_run
124# use gwr_engine::engine::Engine;
125# fn main() {
126# let mut engine = Engine::default();
127# let spawner = engine.spawner();
128let clock = engine.clock_ghz(1.0);
129spawner.spawn(async move {
130  loop {
131    clock.wait_ticks_or_exit(1000).await;
132    println!("Time now {:.2}", clock.time_now_ns());
133  }
134});
135# }
136```
137
138<!-- ANCHOR_END: clock_overview -->
139
140[engine]: ../engine/index.html
141*/
142
143use byte_unit::{AdjustedByte, Byte, UnitType};
144
145pub mod clock;
146pub mod simtime;
147
148// Convert a number of bytes to a binary-only unit (KiB, MiB, etc)
149#[must_use]
150pub fn compute_adjusted_value_and_rate(
151    time_now_ns: f64,
152    num_bytes: usize,
153) -> (AdjustedByte, AdjustedByte) {
154    let time_now_s = time_now_ns / (1000.0 * 1000.0 * 1000.0);
155    let count = Byte::from_u64(num_bytes as u64).get_appropriate_unit(UnitType::Binary);
156    let per_second = if time_now_s == 0.0 {
157        Byte::from_f64(0.0).unwrap()
158    } else {
159        Byte::from_f64(num_bytes as f64 / time_now_s).unwrap()
160    };
161    let count_per_second = per_second.get_appropriate_unit(UnitType::Binary);
162    (count, count_per_second)
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn adjusted_value_and_rate_handles_zero_and_elapsed_time() {
171        let (count, rate) = compute_adjusted_value_and_rate(0.0, 1024);
172        assert_eq!(count.get_value(), 1.0);
173        assert_eq!(rate.get_value(), 0.0);
174
175        let (count, rate) = compute_adjusted_value_and_rate(1_000_000_000.0, 2048);
176        assert_eq!(count.get_value(), 2.0);
177        assert_eq!(rate.get_value(), 2.0);
178    }
179}