gwr_engine/time/
simtime.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3//! This module represents the time during a simulation.
4//!
5//! Time is made up of a cycle count and a phase.
6
7use std::rc::Rc;
8
9use gwr_track::entity::Entity;
10use gwr_track::set_time;
11
12use super::clock::Clock;
13use crate::time::clock::TaskWaker;
14
15/// The overall owner of time within a simulation.
16///
17/// Contains all Clocks and the current simulation time in ns.
18#[derive(Clone)]
19pub struct SimTime {
20    entity: Rc<Entity>,
21
22    current_ns: f64,
23
24    /// Clocks are auto-created as required and kept in a HashMap.
25    ///
26    /// They are hashed using a `u64` which is done in `Hz` so there is a chance
27    /// that a certain clock f
28    clocks: Vec<Clock>,
29}
30
31impl SimTime {
32    #[must_use]
33    pub fn new(parent: &Rc<Entity>) -> Self {
34        Self {
35            entity: Rc::new(Entity::new(parent, "time")),
36            current_ns: 0.0,
37            clocks: Vec::new(),
38        }
39    }
40
41    pub fn get_clock(&mut self, freq_mhz: f64) -> Clock {
42        for clock in &self.clocks {
43            if clock.freq_mhz() == freq_mhz {
44                return clock.clone();
45            }
46        }
47        let clock = Clock::new(freq_mhz);
48        self.clocks.push(clock.clone());
49        clock
50    }
51
52    /// Choose the clock with the next time and return the associated Waker.
53    pub fn advance_time(&mut self) -> Option<Vec<TaskWaker>> {
54        if let Some(next_clock) = self.clocks.iter().min_by(|a, b| a.cmp(b)) {
55            if let Some(clock_time) = next_clock.shared_state.waiting_times.borrow_mut().pop() {
56                let next_ns = next_clock.to_ns(&clock_time);
57                if self.current_ns != next_ns {
58                    set_time!(self.entity ; next_ns);
59                    self.current_ns = next_ns;
60                }
61                next_clock.advance_time(clock_time);
62                next_clock.shared_state.waiting.borrow_mut().pop()
63            } else {
64                None
65            }
66        } else {
67            None
68        }
69    }
70
71    #[must_use]
72    pub fn time_now_ns(&self) -> f64 {
73        self.current_ns
74    }
75
76    /// The simulation can exit if all scheduled tasks can exit.
77    #[must_use]
78    pub fn can_exit(&self) -> bool {
79        for clock in &self.clocks {
80            for waiting in clock.shared_state.waiting.borrow().iter() {
81                for task_waker in waiting {
82                    if !task_waker.can_exit {
83                        // Found one task that must be completed
84                        return false;
85                    }
86                }
87            }
88        }
89        true
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use std::future::Future;
96    use std::pin::Pin;
97    use std::task::{Context, Poll};
98
99    use futures::task::noop_waker;
100    use gwr_track::entity::toplevel;
101    use gwr_track::test_helpers::create_tracker;
102
103    use super::*;
104
105    #[test]
106    fn clock_created_once() {
107        let tracker = create_tracker(file!());
108        let top = toplevel(&tracker, "top");
109
110        let mut time = SimTime::new(&top);
111        let _clk1 = time.get_clock(1000.0);
112        assert_eq!(time.clocks.len(), 1);
113
114        let _clk2 = time.get_clock(1000.0);
115        assert_eq!(time.clocks.len(), 1);
116    }
117
118    #[test]
119    fn create_different_clocks() {
120        let tracker = create_tracker(file!());
121        let top = toplevel(&tracker, "top");
122
123        let mut time = SimTime::new(&top);
124        let _clk1 = time.get_clock(1000.0);
125        assert_eq!(time.clocks.len(), 1);
126
127        let _clk2 = time.get_clock(1800.0);
128        assert_eq!(time.clocks.len(), 2);
129    }
130
131    #[test]
132    fn advance_time_returns_none_without_waiters() {
133        let tracker = create_tracker(file!());
134        let top = toplevel(&tracker, "top");
135
136        let mut time = SimTime::new(&top);
137        assert!(time.advance_time().is_none());
138
139        let _clock = time.get_clock(1000.0);
140        assert!(time.advance_time().is_none());
141    }
142
143    #[test]
144    fn advance_time_handles_equal_times_on_different_clocks() {
145        let tracker = create_tracker(file!());
146        let top = toplevel(&tracker, "top");
147
148        let mut time = SimTime::new(&top);
149        let clock_1ghz = time.get_clock(1000.0);
150        let clock_2ghz = time.get_clock(2000.0);
151        let waker = noop_waker();
152        let mut cx = Context::from_waker(&waker);
153
154        let mut delays = [
155            clock_1ghz.wait_ticks(1),
156            clock_1ghz.wait_ticks(2),
157            clock_2ghz.wait_ticks(2),
158            clock_2ghz.wait_ticks(4),
159        ];
160
161        for delay in &mut delays {
162            assert_eq!(Pin::new(delay).poll(&mut cx), Poll::Pending);
163        }
164
165        for expected_ns in [1.0, 1.0, 2.0, 2.0] {
166            let wakers = time.advance_time().unwrap();
167
168            assert_eq!(wakers.len(), 1);
169            assert_eq!(time.time_now_ns(), expected_ns);
170        }
171
172        assert_eq!(clock_1ghz.tick_now().tick(), 2);
173        assert_eq!(clock_2ghz.tick_now().tick(), 4);
174        assert!(time.advance_time().is_none());
175    }
176}