Skip to main content

gwr_models/fabric/
node.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 number
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 = number of ingress/egress ports.
20//!
21//! The Node is constructed with fabric row and column ports in
22//! addition to the N ingress/egress ports:
23//! ```txt
24//! +-------------------------------------------------------+
25//! | ingress[0..N-1]       row_minus                       |
26//! |                                                       |
27//! | col_minus                                    col_plus |
28//! |                                                       |
29//! | egress[0..N-1]        row_plus                        |
30//! +-------------------------------------------------------+
31//! ```
32
33//! A full fabric model is built of existing limiters, buffers, arbiters and
34//! routers such that the path of a frame from ingress to egress could look
35//! like:
36//!
37//! ```txt
38//!          +-------------------------------------+       +-------------------------------------+
39//!          |              NODE0                  |       |                 NODE1               |
40//!  INGRESS -> LIMIT -> BUF -> ROUTER -> ARBITER -> DELAY -> ROUTER -> ARBITER -> LIMIT -> BUF -> EGRESS
41//!          |                                     |       |                                     |
42//!          +-------------------------------------+       +-------------------------------------+
43//! ```
44
45//! Each [Router] performs the task of taking the frame from an input and
46//! deciding which arbiter to send the frame to. For example, if there were
47//! two fabric ingress/egress ports per node then the router at one of those
48//! ingress ports would look like:
49//!
50//! ```txt
51//!             +--------------------------------------------+
52//!             |                  NODE                      |
53//!             |               +---------------+            |
54//!             |               |     ROUTER    |  ARBITERS  |
55//!             |               |               |            |
56//!             |               |     /-> tx[0] -> col_minus |
57//!             |               |     +-> tx[1] -> col_plus  |
58//!  ingress[0] -> LIMIT -> BUF -> rx +-> tx[2] -> row_minus |
59//!             |               |     +-> tx[3] -> row_plus  |
60//!             |               |     \-> tx[4] -> egress[1] |
61//!             |               +---------------+            |
62//!             +--------------------------------------------+
63//! ```
64
65//! Each [Arbiter] does the job of deciding which frame to send next from
66//! those available on their inputs. For example, again considering a fabric
67//! with two ingress/egress ports per node, the arbiter at one of the egress
68//! ports would look like:
69//!
70//! ```txt
71//!  +-------------------------------------------+
72//!  |                    NODE                   |
73//!  |           +---------------+               |
74//!  |  ROUTERS  |     ARBITER   |               |
75//!  |           |               |               |
76//!  | col_minus -> rx[0] \      |               |
77//!  | col_plus  -> rx[1] +      |               |
78//!  | row_minus -> rx[2] +-> tx -> LIMIT -> BUF -> egress[0]
79//!  | row_plus  -> rx[3] +      |               |
80//!  | egress[1] -> rx[4] /      |               |
81//!  |           +---------------+               |
82//!  +-------------------------------------------+
83//! ```
84
85use std::fmt;
86use std::rc::Rc;
87
88use async_trait::async_trait;
89use clap::ValueEnum;
90use gwr_components::arbiter::Arbiter;
91use gwr_components::arbiter::policy::RoundRobin;
92use gwr_components::flow_controls::limiter::Limiter;
93use gwr_components::router::{Route, Router};
94use gwr_components::store::{ByteStore, Store};
95use gwr_components::{connect_port, rc_limiter};
96use gwr_engine::engine::Engine;
97use gwr_engine::port::PortStateResult;
98use gwr_engine::time::clock::Clock;
99use gwr_engine::traits::{Routable, SimObject};
100use gwr_engine::types::{SimError, SimResult};
101use gwr_model_builder::{EntityDisplay, EntityGet, Runnable};
102use gwr_track::build_aka;
103use gwr_track::entity::Entity;
104use gwr_track::tracker::aka::Aka;
105use serde::{Deserialize, Serialize};
106
107use crate::fabric::FabricConfig;
108
109#[derive(ValueEnum, Clone, Copy, Default, Debug, Serialize, PartialEq, Deserialize)]
110#[serde(rename_all = "kebab-case")]
111pub enum FabricRoutingAlgorithm {
112    /// Route packets to the right column first
113    #[default]
114    ColumnFirst,
115
116    /// Route packets to the right row first
117    RowFirst,
118}
119
120struct NodeRouter {
121    index: usize,
122    node_col: usize,
123    node_row: usize,
124    fabric_algorithm: FabricRoutingAlgorithm,
125    config: Rc<FabricConfig>,
126}
127
128impl<T> Route<T> for NodeRouter
129where
130    T: SimObject + Routable,
131{
132    /// Route an object to the right egress port on the router. The [FabricNode]
133    /// is constructed with [Arbiter]s and [Router]s that have N-1 ports
134    /// (where N is the total number of ports on the [FabricNode]). There
135    /// are N-1 ports because it is invalid to route to oneself.
136    ///
137    /// As a result it is necessary to remap indices from the computed egress
138    /// port to the router port. This depends on the index of this router.
139    fn route(&self, object: &T) -> Result<usize, SimError> {
140        let dest_fabric_port = object.destination() as usize;
141        let (dest_col, dest_row, dest_port) = self
142            .config
143            .fabric_port_index_to_col_row_port(dest_fabric_port);
144        let dest_port = if (self.node_col == dest_col) && (self.node_row == dest_row) {
145            // Local egress
146            dest_port + (Port::Ingress as usize)
147        } else if self.node_col == dest_col {
148            // Column reached, route by row.
149            if self.node_row < dest_row {
150                Port::RowPlus as usize
151            } else {
152                Port::RowMinus as usize
153            }
154        } else if self.node_row == dest_row {
155            // Row reached, route by column.
156            if self.node_col < dest_col {
157                Port::ColPlus as usize
158            } else {
159                Port::ColMinus as usize
160            }
161        } else {
162            // Both row/column not reached. Route according to algorithm.
163            match self.fabric_algorithm {
164                FabricRoutingAlgorithm::ColumnFirst => {
165                    if self.node_col < dest_col {
166                        Port::ColPlus as usize
167                    } else {
168                        Port::ColMinus as usize
169                    }
170                }
171                FabricRoutingAlgorithm::RowFirst => {
172                    if self.node_row < dest_row {
173                        Port::RowPlus as usize
174                    } else {
175                        Port::RowMinus as usize
176                    }
177                }
178            }
179        };
180
181        assert_ne!(
182            dest_port, self.index,
183            "cannot route frame to egress from same port as ingress"
184        );
185
186        // Given there are N-1 ports in routers because they can't route
187        // to themselves we need to exclude the self index.
188        // For example, if there are two ingress/egress ports then different
189        // remappings would look like:
190        //           | port  | Remapped indices with self.index
191        // name      | index | 0, 1, 2, 3, 4, 5
192        // ----------|-------|---------------------------------
193        // col_minus | 0     | -, 0, 0, 0, 0, 0,
194        // col_plus  | 1     | 0, -, 1, 1, 1, 1,
195        // row_minus | 2     | 1, 1, -, 2, 2, 2,
196        // row_plus  | 3     | 2, 2, 2, -, 3, 3,
197        // egress[0] | 4     | 3, 3, 3, 3, -, 4,
198        // egress[1] | 5     | 4, 4, 4, 4, 4, -,
199        if dest_port > self.index {
200            Ok(dest_port - 1)
201        } else {
202            Ok(dest_port)
203        }
204    }
205}
206
207#[repr(usize)]
208#[derive(Copy, Clone, Debug)]
209pub enum Port {
210    ColMinus = 0,
211    ColPlus,
212    RowMinus,
213    RowPlus,
214    Ingress,
215}
216
217impl fmt::Display for Port {
218    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219        // For to_string() use a name in the form of other entities
220        let name = match self {
221            Port::ColMinus => "col_minus",
222            Port::ColPlus => "col_plus",
223            Port::RowMinus => "row_minus",
224            Port::RowPlus => "row_plus",
225            Port::Ingress => "???",
226        };
227        write!(f, "{name}")
228    }
229}
230
231type RouterArbiterResult<T> = (Rc<Arbiter<T>>, Rc<Router<T>>);
232
233#[expect(clippy::too_many_arguments)]
234fn router_arbiter<T>(
235    engine: &Engine,
236    clock: &Clock,
237    node: &Rc<Entity>,
238    config: Rc<FabricConfig>,
239    fabric_algorithm: FabricRoutingAlgorithm,
240    num_arbiter_router_ports: usize,
241    router_arbiter_index: usize,
242    node_col: usize,
243    node_row: usize,
244    name: &str,
245) -> RouterArbiterResult<T>
246where
247    T: SimObject + Routable,
248{
249    let policy = Box::new(RoundRobin::new());
250    let algorithm = Box::new(NodeRouter {
251        index: router_arbiter_index,
252        node_col,
253        node_row,
254        fabric_algorithm,
255        config,
256    });
257    (
258        Arbiter::new_and_register(
259            engine,
260            clock,
261            node,
262            &format!("arb_{name}"),
263            num_arbiter_router_ports,
264            policy,
265        ),
266        Router::new_and_register(
267            engine,
268            clock,
269            node,
270            &format!("router_{name}"),
271            num_arbiter_router_ports,
272            algorithm,
273        ),
274    )
275}
276
277type Arbiters<T> = Vec<Rc<Arbiter<T>>>;
278type Routers<T> = Vec<Rc<Router<T>>>;
279type RoutersArbitersResult<T> = (Arbiters<T>, Routers<T>);
280
281#[expect(clippy::too_many_arguments)]
282fn create_arbiters_routers<T>(
283    engine: &Engine,
284    clock: &Clock,
285    node: &Rc<Entity>,
286    config: &Rc<FabricConfig>,
287    fabric_algorithm: FabricRoutingAlgorithm,
288    num_ingress_egress_ports: usize,
289    node_col: usize,
290    node_row: usize,
291) -> RoutersArbitersResult<T>
292where
293    T: SimObject + Routable,
294{
295    let num_arbiters_routers = Port::Ingress as usize + num_ingress_egress_ports;
296
297    // No need to route to self
298    let num_arbiter_router_ports = num_arbiters_routers - 1;
299
300    let mut arbiters = Vec::with_capacity(num_arbiters_routers);
301    let mut routers = Vec::with_capacity(num_arbiters_routers);
302
303    for (i, port) in vec![Port::ColMinus, Port::ColPlus, Port::RowMinus, Port::RowPlus]
304        .drain(..)
305        .enumerate()
306    {
307        let name = port.to_string();
308        let (arbiter, router) = router_arbiter(
309            engine,
310            clock,
311            node,
312            config.clone(),
313            fabric_algorithm,
314            num_arbiter_router_ports,
315            i,
316            node_col,
317            node_row,
318            name.as_str(),
319        );
320        arbiters.push(arbiter);
321        routers.push(router);
322    }
323
324    for i in 0..num_ingress_egress_ports {
325        let ingress_egress_index = i + Port::Ingress as usize;
326        let policy = Box::new(RoundRobin::new());
327        arbiters.push(Arbiter::new_and_register(
328            engine,
329            clock,
330            node,
331            &format!("arb_{ingress_egress_index}"),
332            num_arbiter_router_ports,
333            policy,
334        ));
335        let algorithm = Box::new(NodeRouter {
336            index: ingress_egress_index,
337            node_col,
338            node_row,
339            fabric_algorithm,
340            config: config.clone(),
341        });
342        routers.push(Router::new_and_register(
343            engine,
344            clock,
345            node,
346            &format!("router_{ingress_egress_index}"),
347            num_arbiter_router_ports,
348            algorithm,
349        ));
350    }
351
352    (arbiters, routers)
353}
354
355type IngressEgressBuffersResult<T> = Result<(Vec<Rc<Limiter<T>>>, Vec<Rc<Store<T>>>), SimError>;
356
357#[expect(clippy::too_many_arguments)]
358fn create_ingress_egress_buffers<T>(
359    engine: &Engine,
360    clock: &Clock,
361    node: &Rc<Entity>,
362    aka: Option<&Aka>,
363    config: &Rc<FabricConfig>,
364    num_ingress_egress_ports: usize,
365    arbiters: &Arbiters<T>,
366    routers: &Routers<T>,
367) -> IngressEgressBuffersResult<T>
368where
369    T: SimObject + Routable,
370{
371    let mut ingress_buffer_limiters = Vec::with_capacity(num_ingress_egress_ports);
372    let mut egress_buffers = Vec::with_capacity(num_ingress_egress_ports);
373
374    let port_limiter = rc_limiter!(clock, config.port_bits_per_tick);
375    for i in 0..num_ingress_egress_ports {
376        let ingress_egress_index = Port::Ingress as usize + i;
377        // Build a buffer per input
378        let ingress_buffer_limiter_aka = build_aka!(aka, node, &[(&format!("ingress_{i}"), "rx")]);
379        let ingress_buffer_limiter = Limiter::new_and_register_with_renames(
380            engine,
381            clock,
382            node,
383            &format!("limit_ingress_{i}"),
384            Some(&ingress_buffer_limiter_aka),
385            port_limiter.clone(),
386        );
387        let ingress_buffer = ByteStore::new_and_register(
388            engine,
389            clock,
390            node,
391            &format!("ingress_buf_{i}"),
392            config.rx_buffer_bytes,
393        )?;
394        connect_port!(ingress_buffer_limiter, tx => ingress_buffer, rx)
395            .expect("Internal ports should connect without error");
396        connect_port!(ingress_buffer, tx => routers[ingress_egress_index], rx)
397            .expect("Internal ports should connect without error");
398        ingress_buffer_limiters.push(ingress_buffer_limiter);
399
400        // Build a buffer per output
401        let egress_buffer_limiter = Limiter::new_and_register(
402            engine,
403            clock,
404            node,
405            &format!("limit_egress_{i}"),
406            port_limiter.clone(),
407        );
408        let egress_buffer_aka = build_aka!(aka, node, &[(&format!("egress_{i}"), "tx")]);
409        let egress_buffer = ByteStore::new_and_register_with_renames(
410            engine,
411            clock,
412            node,
413            &format!("egress_buf_{i}"),
414            Some(&egress_buffer_aka),
415            config.tx_buffer_bytes,
416        )?;
417        connect_port!(egress_buffer_limiter, tx => egress_buffer, rx)
418            .expect("Internal ports should connect without error");
419        connect_port!(arbiters[ingress_egress_index], tx => egress_buffer_limiter, rx)
420            .expect("Internal ports should connect without error");
421        egress_buffers.push(egress_buffer);
422    }
423
424    Ok((ingress_buffer_limiters, egress_buffers))
425}
426
427#[derive(EntityGet, EntityDisplay, Runnable)]
428pub struct FabricNode<T>
429where
430    T: SimObject + Routable,
431{
432    entity: Rc<Entity>,
433
434    arbiters: Vec<Rc<Arbiter<T>>>,
435    routers: Vec<Rc<Router<T>>>,
436
437    ingress_buffer_limiters: Vec<Rc<Limiter<T>>>,
438    egress_buffers: Vec<Rc<Store<T>>>,
439}
440
441impl<T> FabricNode<T>
442where
443    T: SimObject + Routable,
444{
445    #[expect(clippy::too_many_arguments)]
446    pub fn new_and_register_with_renames(
447        engine: &Engine,
448        clock: &Clock,
449        parent: &Rc<Entity>,
450        name: &str,
451        aka: Option<&Aka>,
452        node_col: usize,
453        node_row: usize,
454        config: &Rc<FabricConfig>,
455        fabric_algorithm: FabricRoutingAlgorithm,
456    ) -> Result<Rc<Self>, SimError> {
457        let entity = Rc::new(Entity::new(parent, name));
458
459        let num_ingress_egress_ports = config.node_num_ingress_egress_ports(node_col, node_row);
460
461        let (arbiters, routers) = create_arbiters_routers(
462            engine,
463            clock,
464            &entity,
465            config,
466            fabric_algorithm,
467            num_ingress_egress_ports,
468            node_col,
469            node_row,
470        );
471
472        let (ingress_buffer_limiters, egress_buffers) = create_ingress_egress_buffers(
473            engine,
474            clock,
475            &entity,
476            aka,
477            config,
478            num_ingress_egress_ports,
479            &arbiters,
480            &routers,
481        )?;
482
483        // Perform internal connections from routers -> arbiters
484        for (from, router) in routers.iter().enumerate() {
485            for (to, arbiter) in arbiters.iter().enumerate() {
486                if from == to {
487                    continue;
488                }
489
490                let to_index = if to > from { to - 1 } else { to };
491                let from_index = if from > to { from - 1 } else { from };
492                connect_port!(router, tx, to_index => arbiter, rx, from_index)
493                    .expect("Internal ports should connect without error");
494            }
495        }
496
497        let rc_self = Rc::new(Self {
498            entity,
499            ingress_buffer_limiters,
500            egress_buffers,
501            arbiters,
502            routers,
503        });
504        engine.register(rc_self.clone());
505        Ok(rc_self)
506    }
507
508    #[expect(clippy::too_many_arguments)]
509    pub fn new_and_register(
510        engine: &Engine,
511        clock: &Clock,
512        parent: &Rc<Entity>,
513        name: &str,
514        node_col: usize,
515        node_row: usize,
516        config: &Rc<FabricConfig>,
517        fabric_algorithm: FabricRoutingAlgorithm,
518    ) -> Result<Rc<Self>, SimError> {
519        Self::new_and_register_with_renames(
520            engine,
521            clock,
522            parent,
523            name,
524            None,
525            node_col,
526            node_row,
527            config,
528            fabric_algorithm,
529        )
530    }
531
532    pub fn connect_port_egress_i(&self, i: usize, port_state: PortStateResult<T>) -> SimResult {
533        self.egress_buffers[i].connect_port_tx(port_state)
534    }
535    pub fn port_ingress_i(&self, i: usize) -> PortStateResult<T> {
536        self.ingress_buffer_limiters[i].port_rx()
537    }
538
539    pub fn connect_port_row_minus(&self, port_state: PortStateResult<T>) -> SimResult {
540        self.arbiters[Port::RowMinus as usize].connect_port_tx(port_state)
541    }
542    pub fn connect_port_row_plus(&self, port_state: PortStateResult<T>) -> SimResult {
543        self.arbiters[Port::RowPlus as usize].connect_port_tx(port_state)
544    }
545    pub fn connect_port_col_minus(&self, port_state: PortStateResult<T>) -> SimResult {
546        self.arbiters[Port::ColMinus as usize].connect_port_tx(port_state)
547    }
548    pub fn connect_port_col_plus(&self, port_state: PortStateResult<T>) -> SimResult {
549        self.arbiters[Port::ColPlus as usize].connect_port_tx(port_state)
550    }
551
552    pub fn port_row_minus(&self) -> PortStateResult<T> {
553        self.routers[Port::RowMinus as usize].port_rx()
554    }
555    pub fn port_row_plus(&self) -> PortStateResult<T> {
556        self.routers[Port::RowPlus as usize].port_rx()
557    }
558    pub fn port_col_minus(&self) -> PortStateResult<T> {
559        self.routers[Port::ColMinus as usize].port_rx()
560    }
561    pub fn port_col_plus(&self) -> PortStateResult<T> {
562        self.routers[Port::ColPlus as usize].port_rx()
563    }
564}