Skip to main content

gwr_models/fabric/
functional.rs

1// Copyright (c) 2025 Graphcore Ltd. All rights reserved.
2
3//! A functional implementation of a fabric with very basic timing.
4//!
5//! Assumes that all traffic will move a Manhattan distance through the fabric
6//! to get from ingress to egress.
7//!
8//! The fabric is assumed to be rectangular with a configurable `num_rows` and
9//! `num_columns`. The grid has a configurable number of ports at each node
10//! within the fabric grid.
11//!
12//! # Ports
13//!
14//! Each point in the fabric grid has a configurable numbe
15//!  - N [input ports](gwr_engine::port::InPort): `rx[row][column][0, N-1]`
16//!  - N [output ports](gwr_engine::port::OutPort): `tx[row][column][0, N-1]`
17//!
18//! where:
19//!  - N = num_ports
20
21use std::cell::RefCell;
22use std::collections::VecDeque;
23use std::rc::Rc;
24
25use async_trait::async_trait;
26use gwr_components::flow_controls::limiter::Limiter;
27use gwr_components::router::{DefaultAlgorithm, Route};
28use gwr_components::store::{ByteStore, Store};
29use gwr_components::{connect_port, rc_limiter};
30use gwr_engine::engine::Engine;
31use gwr_engine::events::repeated::Repeated;
32use gwr_engine::executor::Spawner;
33use gwr_engine::port::{InPort, OutPort, PortStateResult};
34use gwr_engine::sim_error;
35use gwr_engine::time::clock::{Clock, ClockTick};
36use gwr_engine::traits::{Event, Routable, Runnable, SimObject};
37use gwr_engine::types::{SimError, SimResult};
38use gwr_model_builder::{EntityDisplay, EntityGet};
39use gwr_track::build_aka;
40use gwr_track::entity::Entity;
41use gwr_track::tracker::aka::Aka;
42
43use crate::fabric::{Fabric, FabricConfig};
44
45/// Return the Manhatten time to travel between RX and TX ports specified.
46#[must_use]
47fn manhatten_rx_to_tx_cycles(
48    config: &FabricConfig,
49    rx_port_index: usize,
50    tx_port_index: usize,
51) -> usize {
52    let (rx_col, rx_row, _) = config.fabric_port_index_to_col_row_port(rx_port_index);
53    let (tx_col, tx_row, _) = config.fabric_port_index_to_col_row_port(tx_port_index);
54    let horizontal_hops = rx_col.abs_diff(tx_col);
55    let vertical_hops = rx_row.abs_diff(tx_row);
56
57    // Add one hop for enterring so that there is never a zero-cycle latency which
58    // could otherwise be seen between ports on the same fabric node
59    (horizontal_hops + vertical_hops) * config.cycles_per_hop + config.cycles_overhead
60}
61
62#[derive(EntityGet, EntityDisplay)]
63pub struct FunctionalFabric<T>
64where
65    T: SimObject + Routable,
66{
67    entity: Rc<Entity>,
68    rx_buffer_limiters: Vec<Rc<Limiter<T>>>,
69    internal_rx: RefCell<Vec<InPort<T>>>,
70    tx_buffers: Vec<Rc<Store<T>>>,
71    internal_tx: RefCell<Vec<OutPort<T>>>,
72    config: Rc<FabricConfig>,
73    clock: Clock,
74    spawner: Spawner,
75}
76
77impl<T> FunctionalFabric<T>
78where
79    T: SimObject + Routable,
80{
81    /// Create and register a new fabric.
82    ///
83    /// The total number of ingress/egress ports must be at least two, otherwise
84    /// there are no valid routes and an error will be returned.
85    pub fn new_and_register_with_renames(
86        engine: &Engine,
87        clock: &Clock,
88        parent: &Rc<Entity>,
89        name: &str,
90        aka: Option<&Aka>,
91        config: Rc<FabricConfig>,
92    ) -> Result<Rc<Self>, SimError> {
93        let entity = Rc::new(Entity::new(parent, name));
94        let spawner = engine.spawner();
95
96        let num_ports = config.num_columns * config.num_rows * config.num_ports_per_node;
97        if num_ports < 2 {
98            return sim_error!("Cannot create fabric with less than 2 ports");
99        }
100
101        let mut rx_buffer_limiters = Vec::with_capacity(num_ports);
102        let mut internal_rx = Vec::with_capacity(num_ports);
103        let mut tx_buffers = Vec::with_capacity(num_ports);
104        let mut internal_tx = Vec::with_capacity(num_ports);
105
106        let port_limiter = rc_limiter!(clock, config.port_bits_per_tick);
107
108        for i in 0..num_ports {
109            // Build a buffer per input
110            let rx_buffer_limiter_aka =
111                build_aka!(aka, &entity, &[(&format!("ingress_{i}"), "rx")]);
112            let rx_buffer_limiter = Limiter::new_and_register_with_renames(
113                engine,
114                clock,
115                &entity,
116                &format!("limit_rx_{i}"),
117                Some(&rx_buffer_limiter_aka),
118                port_limiter.clone(),
119            );
120            let rx_buffer = ByteStore::new_and_register(
121                engine,
122                clock,
123                &entity,
124                &format!("rx_buf_{i}"),
125                config.rx_buffer_bytes,
126            )?;
127            connect_port!(rx_buffer_limiter, tx => rx_buffer, rx)
128                .expect("Internal ports should connect without error");
129
130            // Create and connect a port to receive from the RX
131            let internal_rx_port = InPort::new(engine, clock, &entity, &format!("internal_rx_{i}"));
132            rx_buffer
133                .connect_port_tx(internal_rx_port.state())
134                .expect("Internal ports should connect without error");
135
136            rx_buffer_limiters.push(rx_buffer_limiter);
137            internal_rx.push(internal_rx_port);
138
139            // Build a buffer per output
140            let tx_buffer_limiter = Limiter::new_and_register(
141                engine,
142                clock,
143                &entity,
144                &format!("limit_tx_{i}"),
145                port_limiter.clone(),
146            );
147
148            let tx_buffer_aka = build_aka!(aka, &entity, &[(&format!("egress_{i}"), "tx")]);
149            let tx_buffer = ByteStore::new_and_register_with_renames(
150                engine,
151                clock,
152                &entity,
153                &format!("tx_buf_{i}"),
154                Some(&tx_buffer_aka),
155                config.tx_buffer_bytes,
156            )?;
157            connect_port!(tx_buffer_limiter, tx => tx_buffer, rx)
158                .expect("Internal ports should connect without error");
159
160            // Create and connect a port to drive the TX
161            let mut internal_tx_port = OutPort::new(&entity, &format!("internal_tx_{i}"));
162            internal_tx_port
163                .connect(tx_buffer_limiter.port_rx())
164                .expect("Internal ports should connect without error");
165
166            tx_buffers.push(tx_buffer);
167            internal_tx.push(internal_tx_port);
168        }
169
170        let rc_self = Rc::new(Self {
171            entity,
172            rx_buffer_limiters,
173            internal_rx: RefCell::new(internal_rx),
174            tx_buffers,
175            internal_tx: RefCell::new(internal_tx),
176            config,
177            clock: clock.clone(),
178            spawner,
179        });
180        engine.register(rc_self.clone());
181        Ok(rc_self)
182    }
183
184    /// Create and register a new fabric.
185    ///
186    /// The total number of ingress/egress ports must be at least two, otherwise
187    /// there are no valid routes and an error will be returned.
188    pub fn new_and_register(
189        engine: &Engine,
190        clock: &Clock,
191        parent: &Rc<Entity>,
192        name: &str,
193        config: Rc<FabricConfig>,
194    ) -> Result<Rc<Self>, SimError> {
195        Self::new_and_register_with_renames(engine, clock, parent, name, None, config)
196    }
197}
198
199impl<T> Fabric<T> for FunctionalFabric<T>
200where
201    T: SimObject + Routable,
202{
203    fn connect_port_egress_i(&self, i: usize, port_state: PortStateResult<T>) -> SimResult {
204        self.tx_buffers[i].connect_port_tx(port_state)
205    }
206
207    fn port_ingress_i(&self, i: usize) -> PortStateResult<T> {
208        self.rx_buffer_limiters[i].port_rx()
209    }
210
211    fn col_row_port_to_fabric_port_index(&self, col: usize, row: usize, port: usize) -> usize {
212        self.config
213            .col_row_port_to_fabric_port_index(col, row, port)
214    }
215}
216
217#[async_trait(?Send)]
218impl<T> Runnable for FunctionalFabric<T>
219where
220    T: SimObject + Routable,
221{
222    async fn run(&self) -> SimResult {
223        let num_ports = self.config.max_num_ports();
224        let mut port_states = Vec::with_capacity(num_ports);
225        for _ in 0..num_ports {
226            port_states.push(PortState::default());
227        }
228        let port_states = Rc::new(port_states);
229
230        let routing_algorithm: Rc<Box<dyn Route<T>>> = Rc::new(Box::new(DefaultAlgorithm {}));
231
232        for (i, internal_rx) in self.internal_rx.borrow_mut().drain(..).enumerate() {
233            let entity = self.entity.clone();
234            let clock = self.clock.clone();
235            let port_states = port_states.clone();
236            let routing_algorithm = routing_algorithm.clone();
237            let config = self.config.clone();
238
239            self.spawner.spawn(async move {
240                run_rx(
241                    entity,
242                    clock,
243                    i,
244                    internal_rx,
245                    port_states,
246                    routing_algorithm,
247                    config,
248                )
249                .await
250            });
251        }
252
253        for (i, internal_tx) in self.internal_tx.borrow_mut().drain(..).enumerate() {
254            let entity = self.entity.clone();
255            let clock = self.clock.clone();
256            let port_states = port_states.clone();
257
258            self.spawner
259                .spawn(async move { run_tx(entity, clock, i, internal_tx, port_states).await });
260        }
261
262        Ok(())
263    }
264}
265
266/// Structure containing all shared common state for the fabric
267///
268/// This allows it to be easily shared across all rx and tx handlers.
269struct PortState<T> {
270    data_for_tx: RefCell<VecDeque<(T, ClockTick)>>,
271    data_for_tx_bytes: RefCell<usize>,
272    waiting_for_data: Repeated<()>,
273    waiting_for_room: Repeated<()>,
274    inputs_waiting_for_room: RefCell<VecDeque<usize>>,
275}
276
277impl<T> Default for PortState<T> {
278    fn default() -> Self {
279        Self {
280            data_for_tx: RefCell::new(VecDeque::new()),
281            data_for_tx_bytes: RefCell::new(0),
282            waiting_for_data: Repeated::default(),
283            waiting_for_room: Repeated::default(),
284            inputs_waiting_for_room: RefCell::new(VecDeque::new()),
285        }
286    }
287}
288
289async fn run_rx<T>(
290    entity: Rc<Entity>,
291    clock: Clock,
292    port_index: usize,
293    mut internal_rx: InPort<T>,
294    port_states: Rc<Vec<PortState<T>>>,
295    routing_algorithm: Rc<Box<dyn Route<T>>>,
296    config: Rc<FabricConfig>,
297) -> SimResult
298where
299    T: SimObject + Routable,
300{
301    // Use the size of the TX buffer to configure the internal buffering.
302    let max_internal_buffer_bytes = config.tx_buffer_bytes;
303
304    loop {
305        let value = internal_rx.get()?.await;
306        let value_id = value.id();
307        entity.track_enter(value_id);
308        let value_bytes = value.total_bytes();
309
310        let dest_index = routing_algorithm.route(&value)?;
311        let delay_ticks = manhatten_rx_to_tx_cycles(&config, port_index, dest_index);
312
313        let mut tick = clock.tick_now();
314        tick.set_tick(tick.tick() + delay_ticks as u64);
315
316        // If the queue to the destination is too full then wait for space
317        while *port_states[dest_index].data_for_tx_bytes.borrow() + value_bytes
318            > max_internal_buffer_bytes
319        {
320            port_states[dest_index]
321                .inputs_waiting_for_room
322                .borrow_mut()
323                .push_back(port_index);
324            port_states[port_index].waiting_for_room.listen().await;
325        }
326        *port_states[dest_index].data_for_tx_bytes.borrow_mut() += value_bytes;
327        port_states[dest_index]
328            .data_for_tx
329            .borrow_mut()
330            .push_back((value, tick));
331        port_states[dest_index].waiting_for_data.notify();
332    }
333}
334
335async fn run_tx<T>(
336    entity: Rc<Entity>,
337    clock: Clock,
338    port_index: usize,
339    mut internal_tx: OutPort<T>,
340    port_states: Rc<Vec<PortState<T>>>,
341) -> SimResult
342where
343    T: SimObject + Routable,
344{
345    loop {
346        let next = port_states[port_index].data_for_tx.borrow_mut().pop_front();
347
348        if let Some((value, _)) = &next {
349            *port_states[port_index].data_for_tx_bytes.borrow_mut() -= value.total_bytes();
350        }
351
352        if let Some(waiting_input) = port_states[port_index]
353            .inputs_waiting_for_room
354            .borrow_mut()
355            .pop_front()
356        {
357            port_states[waiting_input].waiting_for_room.notify();
358        }
359
360        match next {
361            Some((value, tick)) => {
362                let tick_now = clock.tick_now();
363                if tick_now.tick() < tick.tick() {
364                    // Need to send in the future, delay
365                    clock.wait_ticks(tick.tick() - tick_now.tick()).await;
366                }
367
368                entity.track_exit(value.id());
369                internal_tx.put(value)?.await;
370            }
371            None => {
372                port_states[port_index].waiting_for_data.listen().await;
373            }
374        }
375    }
376}