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 an `Rc<RateLimiter>`
24//! because that is what components normally accept as a rate limiter argument.
25//! They are `Rc`ed because they are used immutably and as a result the same
26//! rate limiter can be shared by all components that have the same bandwidth.
27//!
28//! # Examples:
29//!
30//! Here is a basic example of a rate limiter being used by the
31//! [Limiter](crate::flow_controls::limiter) component which is connected
32//! between a source and sink.
33//!
34//! A [source](crate::source::Source) is used to produce 4-byte packets.
35//! The rate limiter is configured to run on a 1GHz clock at a rate of 16 bits
36//! per tick.
37//!
38//! As a result, the total time for the simulation should be `20.0ns` because
39//! each of the 10 packets should take 2 clock ticks to pass through the
40//! [Limiter](crate::flow_controls::limiter) and be consumed by the
41//! [Sink](crate::sink::Sink).
42//!
43//! ```rust
44//! # use gwr_components::flow_controls::limiter::Limiter;
45//! # use gwr_components::sink::Sink;
46//! # use gwr_components::source::Source;
47//! # use gwr_components::{connect_port, rc_limiter, option_box_repeat};
48//! # use gwr_engine::engine::Engine;
49//! # use gwr_engine::run_simulation;
50//! # use gwr_track::entity::GetEntity;
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);
60//!
61//! // Build the source (initially with no generator).
62//! let source = Source::new_and_register(&engine, engine.top(), "source", None)
63//!     .expect("should be able to create and register `Source`");
64//!
65//! // Create a packet that uses the source as its trace-control entity.
66//! let packet = 0; // TODO implement a packet type to use here
67//!
68//! // Configure the source to produce ten of these packets.
69//! source.set_generator(option_box_repeat!(packet ; 10));
70//!
71//! // Create the a limiter component to enforce the limit
72//! let limiter = Limiter::new_and_register(&engine, &clock, engine.top(), "limit", rate_limiter)
73//!     .expect("should be able to create and register `Limiter`");
74//!
75//! // Create the sink to accept these packets.
76//! let sink = Sink::new_and_register(&engine, &clock, engine.top(), "sink")
77//!     .expect("should be able to create and register `Sink`");
78//!
79//! // Connect the components.
80//! connect_port!(source, tx => limiter, rx)
81//!     .expect("should be able to connect `Source` to `Limiter`");
82//! connect_port!(limiter, tx => sink, rx)
83//!     .expect("should be able to connect `Limiter` to `Sink`");
84//!
85//! // Run the simulation.
86//! run_simulation!(engine);
87//!
88//! // Ensure the time is as expected.
89//! assert_eq!(engine.time_now_ns(), 20.0);
90//! ```
91
92use std::marker::PhantomData;
93
94use gwr_engine::time::clock::Clock;
95use gwr_engine::traits::TotalBytes;
96
97/// Create a [RateLimiter] wrapped in an [Rc](std::rc::Rc).
98///
99/// This is the most common form of [RateLimiter] used because all of its
100/// methods can be used immutably and therefore it can be shared by any number
101/// of components with the same bandwidth limit.
102#[macro_export]
103macro_rules! rc_limiter {
104    ($clock:expr, $bits_per_tick:expr) => {
105        std::rc::Rc::new($crate::flow_controls::rate_limiter::RateLimiter::new(
106            $clock,
107            $bits_per_tick,
108        ))
109    };
110}
111
112#[macro_export]
113macro_rules! option_rc_limiter {
114    ($clock:expr, $bits_per_tick:expr) => {
115        Some(std::rc::Rc::new(
116            $crate::flow_controls::rate_limiter::RateLimiter::new($clock, $bits_per_tick),
117        ))
118    };
119}
120
121#[derive(Clone)]
122pub struct RateLimiter<T>
123where
124    T: TotalBytes,
125{
126    /// Clock rate limiter is attached to.
127    clock: Clock,
128
129    /// Bits per tick that can pass through this interface.
130    bits_per_tick: usize,
131
132    phantom: PhantomData<T>,
133}
134
135impl<T> RateLimiter<T>
136where
137    T: TotalBytes,
138{
139    #[must_use]
140    pub fn new(clock: &Clock, bits_per_tick: usize) -> Self {
141        Self {
142            clock: clock.clone(),
143            bits_per_tick,
144            phantom: PhantomData,
145        }
146    }
147
148    pub async fn delay(&self, value: &T) {
149        let delay_ticks = self.ticks(value);
150        self.clock.wait_ticks(delay_ticks as u64).await;
151    }
152
153    pub async fn delay_ticks(&self, ticks: usize) {
154        self.clock.wait_ticks(ticks as u64).await;
155    }
156
157    pub fn ticks(&self, value: &T) -> usize {
158        let payload_bytes = value.total_bytes();
159        let payload_bits = payload_bytes * 8;
160        self.ticks_from_bits(payload_bits)
161    }
162
163    #[must_use]
164    pub fn ticks_from_bits(&self, bits: usize) -> usize {
165        bits.div_ceil(self.bits_per_tick)
166    }
167}