Skip to main content

gwr_models/fabric/
routed.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_columns`
9//! and `num_rows`. 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 (col, row) has N ingress and egress ports:
15//!  - N [ingress ports](gwr_engine::port::InPort): `ingress[col][row][0, N-1]`
16//!  - N [egress ports](gwr_engine::port::OutPort): `egress[col][row][0, N-1]`
17//!
18//! In order to connect to the fabric use the
19//! `col_row_port_to_fabric_port_index()` function in the configuration
20//! structure to get the index of the port you want to connect to.
21
22use std::rc::Rc;
23
24use async_trait::async_trait;
25use gwr_components::delay::Delay;
26use gwr_components::{connect_dummy_rx, connect_dummy_tx, connect_port};
27use gwr_engine::engine::Engine;
28use gwr_engine::port::PortStateResult;
29use gwr_engine::sim_error;
30use gwr_engine::time::clock::Clock;
31use gwr_engine::traits::{Routable, SimObject};
32use gwr_engine::types::{SimError, SimResult};
33use gwr_model_builder::{EntityDisplay, EntityGet, Runnable};
34use gwr_track::entity::Entity;
35use gwr_track::tracker::aka::{Aka, populate_aka_from_string};
36
37use crate::fabric::node::{FabricNode, FabricRoutingAlgorithm};
38use crate::fabric::{Fabric, FabricConfig};
39
40#[derive(EntityGet, EntityDisplay, Runnable)]
41pub struct RoutedFabric<T>
42where
43    T: SimObject + Routable,
44{
45    entity: Rc<Entity>,
46    nodes: Vec<Vec<Rc<FabricNode<T>>>>,
47    config: Rc<FabricConfig>,
48}
49
50fn build_node_aka(
51    entity: &Rc<Entity>,
52    aka: Option<&Aka>,
53    new_aka: &mut Aka,
54    col: usize,
55    row: usize,
56    config: &Rc<FabricConfig>,
57) {
58    let mut renames = Vec::new();
59    for port in 0..config.num_ports_per_node() {
60        let fabric_port_index = config.col_row_port_to_fabric_port_index(col, row, port);
61        renames.push((
62            format!("ingress_{fabric_port_index}"),
63            format!("ingress_{port}"),
64        ));
65        renames.push((
66            format!("egress_{fabric_port_index}"),
67            format!("egress_{port}"),
68        ));
69    }
70    populate_aka_from_string(aka, Some(new_aka), entity, &renames);
71}
72
73type FabricNodesResult<T> = Result<Vec<Vec<Rc<FabricNode<T>>>>, SimError>;
74
75fn create_nodes<T>(
76    engine: &Engine,
77    clock: &Clock,
78    entity: &Rc<Entity>,
79    aka: Option<&Aka>,
80    config: &Rc<FabricConfig>,
81    fabric_algorithm: FabricRoutingAlgorithm,
82) -> FabricNodesResult<T>
83where
84    T: SimObject + Routable,
85{
86    let num_columns = config.num_columns();
87    let num_rows = config.num_rows();
88    let mut nodes = Vec::with_capacity(num_columns);
89
90    for c in 0..num_columns {
91        let mut col_nodes = Vec::with_capacity(num_rows);
92        for r in 0..num_rows {
93            let mut new_aka = Aka::default();
94            build_node_aka(entity, aka, &mut new_aka, c, r, config);
95            let node = FabricNode::new_and_register_with_renames(
96                engine,
97                clock,
98                entity,
99                &format!("node_{c}_{r}"),
100                Some(&new_aka),
101                c,
102                r,
103                config,
104                fabric_algorithm,
105            )?;
106            col_nodes.push(node);
107        }
108        nodes.push(col_nodes);
109    }
110    Ok(nodes)
111}
112
113/// Create connections between columns
114fn connect_columns<T>(
115    engine: &Engine,
116    clock: &Clock,
117    entity: &Rc<Entity>,
118    config: &Rc<FabricConfig>,
119    nodes: &[Vec<Rc<FabricNode<T>>>],
120    delay_ticks: usize,
121) where
122    T: SimObject + Routable,
123{
124    for c in 1..config.num_columns {
125        let c_m1 = c - 1;
126        // Clippy suggestion to avoid needless_range_loop results in an unused
127        // variable from the iterator and therefore a larger refactor is likely
128        // required here. There may be hazards to be aware of when attempting
129        // to resolve this, such as highlighted in
130        // https://github.com/rust-lang/rust-clippy/issues/16344.
131        #[expect(clippy::needless_range_loop)]
132        for r in 0..config.num_rows {
133            let delay = Delay::new_and_register(
134                engine,
135                clock,
136                entity,
137                &format!("{c_m1}_{r}_to_{c}_{r}"),
138                delay_ticks,
139            );
140            connect_port!(nodes[c_m1][r], col_plus => delay, rx)
141                .expect("Internal ports should connect without error");
142            connect_port!(delay, tx => nodes[c][r], col_minus)
143                .expect("Internal ports should connect without error");
144
145            let delay = Delay::new_and_register(
146                engine,
147                clock,
148                entity,
149                &format!("{c}_{r}_to_{c_m1}_{r}"),
150                delay_ticks,
151            );
152            connect_port!(nodes[c][r], col_minus => delay, rx)
153                .expect("Internal ports should connect without error");
154            connect_port!(delay, tx => nodes[c_m1][r], col_plus)
155                .expect("Internal ports should connect without error");
156        }
157    }
158}
159
160/// Create connections between rows
161fn connect_rows<T>(
162    engine: &Engine,
163    clock: &Clock,
164    entity: &Rc<Entity>,
165    config: &Rc<FabricConfig>,
166    nodes: &[Vec<Rc<FabricNode<T>>>],
167    delay_ticks: usize,
168) where
169    T: SimObject + Routable,
170{
171    for (c, col) in nodes.iter().enumerate() {
172        for r in 1..config.num_rows {
173            let r_m1 = r - 1;
174            let delay = Delay::new_and_register(
175                engine,
176                clock,
177                entity,
178                &format!("{c}_{r_m1}_to_{c}_{r}"),
179                delay_ticks,
180            );
181            connect_port!(col[r_m1], row_plus => delay, rx)
182                .expect("Internal ports should connect without error");
183            connect_port!(delay, tx => col[r], row_minus)
184                .expect("Internal ports should connect without error");
185
186            let delay = Delay::new_and_register(
187                engine,
188                clock,
189                entity,
190                &format!("{c}_{r}_to_{c}_{r_m1}"),
191                delay_ticks,
192            );
193            connect_port!(col[r], row_minus => delay, rx)
194                .expect("Internal ports should connect without error");
195            connect_port!(delay, tx => col[r_m1], row_plus)
196                .expect("Internal ports should connect without error");
197        }
198    }
199}
200
201/// Connect up the edge ports that will otherwise be left dangling
202fn create_dummy_ports<T>(
203    engine: &Engine,
204    clock: &Clock,
205    entity: &Rc<Entity>,
206    config: &Rc<FabricConfig>,
207    nodes: &[Vec<Rc<FabricNode<T>>>],
208) where
209    T: SimObject + Routable,
210{
211    // Connect dummy ports left/right
212    let right = config.num_columns - 1;
213    // Clippy suggestion to avoid needless_range_loop doesn't account for the
214    // way nodes is accessed, i.e. nodes[0][r] and nodes[right][r] and therefore
215    // a larger refactor is likely required here.
216    #[expect(clippy::needless_range_loop)]
217    for r in 0..config.num_rows {
218        connect_dummy_tx!(entity => nodes[0][r], col_minus)
219            .expect("Internal ports should connect without error");
220        connect_dummy_rx!(nodes[0][r], col_minus => engine, clock, entity)
221            .expect("Internal ports should connect without error");
222        connect_dummy_tx!(entity => nodes[right][r], col_plus)
223            .expect("Internal ports should connect without error");
224        connect_dummy_rx!(nodes[right][r], col_plus => engine, clock, entity)
225            .expect("Internal ports should connect without error");
226    }
227
228    // Connect dummy ports top/bottom
229    let bottom = config.num_rows - 1;
230    for col in nodes {
231        connect_dummy_tx!(entity => col[0], row_minus)
232            .expect("Internal ports should connect without error");
233        connect_dummy_rx!(col[0], row_minus => engine, clock, entity)
234            .expect("Internal ports should connect without error");
235        connect_dummy_tx!(entity => col[bottom], row_plus)
236            .expect("Internal ports should connect without error");
237        connect_dummy_rx!(col[bottom], row_plus => engine, clock, entity)
238            .expect("Internal ports should connect without error");
239    }
240}
241
242impl<T> RoutedFabric<T>
243where
244    T: SimObject + Routable,
245{
246    pub fn new_and_register_with_renames(
247        engine: &Engine,
248        clock: &Clock,
249        parent: &Rc<Entity>,
250        name: &str,
251        aka: Option<&Aka>,
252        config: Rc<FabricConfig>,
253        fabric_algorithm: FabricRoutingAlgorithm,
254    ) -> Result<Rc<Self>, SimError> {
255        let entity = Rc::new(Entity::new(parent, name));
256        let num_ports = config.num_columns * config.num_rows * config.num_ports_per_node;
257        if num_ports < 2 {
258            return sim_error!("Cannot create fabric with less than 2 ports");
259        }
260
261        let nodes = create_nodes(engine, clock, &entity, aka, &config, fabric_algorithm)?;
262        connect_columns(
263            engine,
264            clock,
265            &entity,
266            &config,
267            &nodes,
268            config.cycles_per_hop,
269        );
270        connect_rows(
271            engine,
272            clock,
273            &entity,
274            &config,
275            &nodes,
276            config.cycles_per_hop,
277        );
278        create_dummy_ports(engine, clock, &entity, &config, &nodes);
279
280        let rc_self = Rc::new(Self {
281            entity,
282            nodes,
283            config,
284        });
285
286        engine.register(rc_self.clone());
287        Ok(rc_self)
288    }
289
290    pub fn new_and_register(
291        engine: &Engine,
292        clock: &Clock,
293        parent: &Rc<Entity>,
294        name: &str,
295        config: Rc<FabricConfig>,
296        fabric_algorithm: FabricRoutingAlgorithm,
297    ) -> Result<Rc<Self>, SimError> {
298        Self::new_and_register_with_renames(
299            engine,
300            clock,
301            parent,
302            name,
303            None,
304            config,
305            fabric_algorithm,
306        )
307    }
308}
309
310impl<T> Fabric<T> for RoutedFabric<T>
311where
312    T: SimObject + Routable,
313{
314    fn connect_port_egress_i(&self, i: usize, port_state: PortStateResult<T>) -> SimResult {
315        let (c, r, p) = self.config.fabric_port_index_to_col_row_port(i);
316        self.nodes[c][r].connect_port_egress_i(p, port_state)
317    }
318
319    fn port_ingress_i(&self, i: usize) -> PortStateResult<T> {
320        let (c, r, p) = self.config.fabric_port_index_to_col_row_port(i);
321        self.nodes[c][r].port_ingress_i(p)
322    }
323
324    fn col_row_port_to_fabric_port_index(&self, col: usize, row: usize, port: usize) -> usize {
325        self.config
326            .col_row_port_to_fabric_port_index(col, row, port)
327    }
328}