Skip to main content

gwr_components/flow_controls/
rate_limiter.rs

1// Copyright (c) 2023 Graphcore Ltd. All rights reserved.
2
3//! Provide effective bandwidth limit for a component.
4//!
5//! A [RateLimiter] is a component that is given a
6//! [clock](gwr_engine::time::clock::Clock) and a rate in `bits per tick`.
7//! It uses this rate limit to enforce a delay determined by the object that is
8//! being rate limited.
9//!
10//! The [RateLimiter] therefore requires objects to implement the
11//! [TotalBytes] trait so that the number of bits of the object can be
12//! determined.
13//!
14//! # Ports
15//!
16//! This component has the following ports:
17//!  - One [input port](gwr_engine::port::InPort): `rx`
18//!  - One [output port](gwr_engine::port::OutPort): `tx`
19//!
20//! # Creating a Rate Limiter
21//!
22//! [RateLimiter]s should normally be constructed using the
23//! [rc_limiter!](crate::rc_limiter) macro. This returns a
24//! `Result<Rc<RateLimiter>, SimError>`, rejecting a zero rate. Components
25//! normally accept an `Rc<RateLimiter>` as a rate limiter argument.
26//! They are `Rc`ed because they are used immutably and as a result the same
27//! rate limiter can be shared by all components that have the same bandwidth.
28//!
29//! # Examples:
30//!
31//! Here is a basic example of a rate limiter being used by the
32//! [Limiter](crate::flow_controls::limiter) component which is connected
33//! between a source and sink.
34//!
35//! A [source](crate::source::Source) is used to produce 4-byte packets.
36//! The rate limiter is configured to run on a 1GHz clock at a rate of 16 bits
37//! per tick.
38//!
39//! As a result, the total time for the simulation should be `20.0ns` because
40//! each of the 10 packets should take 2 clock ticks to pass through the
41//! [Limiter](crate::flow_controls::limiter) and be consumed by the
42//! [Sink](crate::sink::Sink).
43//!
44//! ```rust
45//! # use gwr_components::flow_controls::limiter::Limiter;
46//! # use gwr_components::sink::Sink;
47//! # use gwr_components::source::Source;
48//! # use gwr_components::{connect_port, rc_limiter, option_box_repeat};
49//! # use gwr_engine::engine::Engine;
50//! # use gwr_engine::run_simulation;
51//!
52//! // Create the engine.
53//! let mut engine = Engine::default();
54//!
55//! // Create a 1GHz clock.
56//! let clock = engine.clock_ghz(1.0);
57//!
58//! // And build a 16 bits-per-tick rate limiter.
59//! let rate_limiter = rc_limiter!(&clock, 16).unwrap();
60//!
61//! // Build the source (initially with no generator).
62//! let source = Source::new_and_register(&engine, engine.top(), "source", None);
63//!
64//! // Create a packet that uses the source as its trace-control entity.
65//! let packet = 0; // TODO implement a packet type to use here
66//!
67//! // Configure the source to produce ten of these packets.
68//! source.set_generator(option_box_repeat!(packet ; 10));
69//!
70//! // Create the a limiter component to enforce the limit
71//! let limiter = Limiter::new_and_register(&engine, &clock, engine.top(), "limit", rate_limiter);
72//!
73//! // Create the sink to accept these packets.
74//! let sink = Sink::new_and_register(&engine, &clock, engine.top(), "sink");
75//!
76//! // Connect the components.
77//! connect_port!(source, tx => limiter, rx)
78//!     .expect("should be able to connect `Source` to `Limiter`");
79//! connect_port!(limiter, tx => sink, rx)
80//!     .expect("should be able to connect `Limiter` to `Sink`");
81//!
82//! // Run the simulation.
83//! run_simulation!(engine);
84//!
85//! // Ensure the time is as expected.
86//! assert_eq!(engine.time_now_ns(), 20.0);
87//! ```
88
89use std::marker::PhantomData;
90
91use gwr_engine::time::clock::Clock;
92use gwr_engine::traits::TotalBytes;
93use gwr_engine::types::{SimError, SimResult};
94
95/// Create a [RateLimiter] wrapped in an [Rc](std::rc::Rc), returning a
96/// [`SimError`] if the rate is zero.
97///
98/// This is the most common form of [RateLimiter] used because all of its
99/// methods can be used immutably and therefore it can be shared by any number
100/// of components with the same bandwidth limit.
101#[macro_export]
102macro_rules! rc_limiter {
103    ($clock:expr, $bits_per_tick:expr) => {
104        $crate::flow_controls::rate_limiter::RateLimiter::new($clock, $bits_per_tick)
105            .map(std::rc::Rc::new)
106    };
107}
108
109#[macro_export]
110macro_rules! option_rc_limiter {
111    ($clock:expr, $bits_per_tick:expr) => {
112        $crate::rc_limiter!($clock, $bits_per_tick).map(Some)
113    };
114}
115
116#[derive(Clone)]
117pub struct RateLimiter<T>
118where
119    T: TotalBytes,
120{
121    /// Clock rate limiter is attached to.
122    clock: Clock,
123
124    /// Bits per tick that can pass through this interface.
125    bits_per_tick: usize,
126
127    phantom: PhantomData<T>,
128}
129
130impl<T> RateLimiter<T>
131where
132    T: TotalBytes,
133{
134    /// Create a rate limiter, rejecting a zero rate.
135    pub fn new(clock: &Clock, bits_per_tick: usize) -> Result<Self, SimError> {
136        if bits_per_tick == 0 {
137            return Err(SimError("rate limiter rate must be positive".into()));
138        }
139        Ok(Self {
140            clock: clock.clone(),
141            bits_per_tick,
142            phantom: PhantomData,
143        })
144    }
145
146    /// Wait for the packet's service duration, rejecting invalid arithmetic.
147    pub async fn delay(&self, value: &T) -> SimResult {
148        self.delay_ticks(self.ticks(value)?).await
149    }
150
151    /// Wait for a service duration, checking the deadline at the wait point.
152    pub async fn delay_ticks(&self, ticks: u64) -> SimResult {
153        self.clock.wait_ticks(ticks)?.await;
154        Ok(())
155    }
156
157    /// Calculate a packet's service duration without overflowing its bit count.
158    pub fn ticks(&self, value: &T) -> Result<u64, SimError> {
159        let bits = value
160            .total_bytes()
161            .checked_mul(8)
162            .ok_or_else(|| SimError("rate limiter packet bit count overflows".into()))?;
163        self.ticks_from_bits(bits)
164    }
165
166    /// Round up to whole ticks, rejecting an unrepresentable duration.
167    pub fn ticks_from_bits(&self, bits: usize) -> Result<u64, SimError> {
168        u64::try_from(bits.div_ceil(self.bits_per_tick))
169            .map_err(|_| SimError("rate limiter duration exceeds u64".into()))
170    }
171}