Skip to main content

gwr_models/fabric/
mod.rs

1// Copyright (c) 2025 Graphcore Ltd. All rights reserved.
2
3//! Models of fabric interconnects.
4//!
5//! For simplicity, fabrics are assumed to be rectangular (N columns x M rows)
6//! collections of nodes with each node allocated P ingress/egress port IDs.
7//! However, if the user limits the number of ports per node then not all
8//! ingress/egress ports will be populated.
9
10use std::cmp::min;
11use std::fmt::Display;
12
13use gwr_engine::port::PortStateResult;
14use gwr_engine::traits::{Routable, SimObject};
15use gwr_engine::types::SimResult;
16use gwr_track::entity::GetEntity;
17
18pub trait Fabric<T>: GetEntity + Display
19where
20    T: SimObject + Routable,
21{
22    fn connect_port_egress_i(&self, i: usize, port_state: PortStateResult<T>) -> SimResult;
23    fn port_ingress_i(&self, i: usize) -> PortStateResult<T>;
24    fn col_row_port_to_fabric_port_index(&self, col: usize, row: usize, port: usize) -> usize;
25}
26
27/// Configuration structure for a fabric
28pub struct FabricConfig {
29    /// Number of columns in the fabric
30    num_columns: usize,
31
32    /// Number of rows in the fabric
33    num_rows: usize,
34
35    /// Number of ingress/egress port pairs at each node of the fabric
36    num_ports_per_node: usize,
37
38    /// Optional limit to total number of ports on a node. Depending on
39    /// where in the fabric a node is there will be up to 4 internal ports
40    /// already used for x/y routing.
41    ports_per_node_limit: Option<usize>,
42
43    /// Cycles per hop when routing between an ingress and egress port
44    cycles_per_hop: usize,
45
46    /// Fixed overhead to be added to routing delay
47    cycles_overhead: usize,
48
49    /// Number of bytes in the rx buffer for each fabric port
50    rx_buffer_bytes: usize,
51
52    /// Number of bytes in the tx buffer for each fabric port
53    tx_buffer_bytes: usize,
54
55    /// Set the throughput limit on each port (in bits per tick)
56    port_bits_per_tick: usize,
57
58    /// Indices of populated ingress/egress ports
59    fabric_port_indices: Vec<usize>,
60}
61
62#[must_use]
63fn col_row_port_to_fabric_port_index(
64    num_rows: usize,
65    num_ports_per_node: usize,
66    col: usize,
67    row: usize,
68    port: usize,
69) -> usize {
70    port + row * num_ports_per_node + col * num_rows * num_ports_per_node
71}
72
73#[must_use]
74fn num_x_y_ports(num_columns: usize, num_rows: usize, col: usize, row: usize) -> usize {
75    let mut num_ports = 4;
76    if col == 0 || col == num_columns - 1 {
77        // Left/right edge
78        num_ports -= 1;
79    }
80    if row == 0 || row == num_rows - 1 {
81        // Top/bottom edge
82        num_ports -= 1;
83    }
84    num_ports
85}
86
87/// Given a col/row position of a node in a fabric, compute how many
88/// ingress/egress ports there are
89#[must_use]
90fn node_num_ingress_egress_ports(
91    num_columns: usize,
92    num_rows: usize,
93    num_ports_per_node: usize,
94    ports_per_node_limit: Option<usize>,
95    col: usize,
96    row: usize,
97) -> usize {
98    match ports_per_node_limit {
99        None => num_ports_per_node,
100        Some(ports_per_node_limit) => {
101            let num_x_y_ports = num_x_y_ports(num_columns, num_rows, col, row);
102            let max_ingress_egress_ports = ports_per_node_limit.saturating_sub(num_x_y_ports);
103            min(max_ingress_egress_ports, num_ports_per_node)
104        }
105    }
106}
107
108fn create_populated_indices(
109    num_columns: usize,
110    num_rows: usize,
111    num_ports_per_node: usize,
112    ports_per_node_limit: Option<usize>,
113) -> Vec<usize> {
114    let mut fabric_indices = Vec::new();
115    for col in 0..num_columns {
116        for row in 0..num_rows {
117            let num_ports = node_num_ingress_egress_ports(
118                num_columns,
119                num_rows,
120                num_ports_per_node,
121                ports_per_node_limit,
122                col,
123                row,
124            );
125            for port in 0..num_ports {
126                fabric_indices.push(col_row_port_to_fabric_port_index(
127                    num_rows,
128                    num_ports_per_node,
129                    col,
130                    row,
131                    port,
132                ));
133            }
134        }
135    }
136    fabric_indices
137}
138
139impl FabricConfig {
140    #[expect(clippy::too_many_arguments)]
141    #[must_use]
142    pub fn new(
143        num_columns: usize,
144        num_rows: usize,
145        num_ports_per_node: usize,
146        ports_per_node_limit: Option<usize>,
147        cycles_per_hop: usize,
148        cycles_overhead: usize,
149        rx_buffer_bytes: usize,
150        tx_buffer_bytes: usize,
151        port_bits_per_tick: usize,
152    ) -> Self {
153        let fabric_port_indices = create_populated_indices(
154            num_columns,
155            num_rows,
156            num_ports_per_node,
157            ports_per_node_limit,
158        );
159        Self {
160            num_columns,
161            num_rows,
162            num_ports_per_node,
163            ports_per_node_limit,
164            cycles_per_hop,
165            cycles_overhead,
166            rx_buffer_bytes,
167            tx_buffer_bytes,
168            port_bits_per_tick,
169            fabric_port_indices,
170        }
171    }
172
173    /// Returns the maximum number of ports in the fabric
174    #[must_use]
175    pub fn max_num_ports(&self) -> usize {
176        self.num_columns * self.num_rows * self.num_ports_per_node
177    }
178
179    /// Returns the number of ports in a fabric.
180    #[must_use]
181    pub fn num_ports(&self) -> usize {
182        self.fabric_port_indices.len()
183    }
184
185    /// Returns the actual port indices
186    #[must_use]
187    pub fn port_indices(&self) -> &Vec<usize> {
188        &self.fabric_port_indices
189    }
190
191    /// Given a column, row and port index, return the overall index in the
192    /// fabric ports
193    ///
194    /// Ports laid out as
195    /// ports\[col\]\[row\]\[port\]
196    #[must_use]
197    pub fn col_row_port_to_fabric_port_index(&self, col: usize, row: usize, port: usize) -> usize {
198        col_row_port_to_fabric_port_index(self.num_rows, self.num_ports_per_node, col, row, port)
199    }
200
201    #[must_use]
202    pub fn fabric_port_index_to_col_row_port(
203        &self,
204        fabric_port_index: usize,
205    ) -> (usize, usize, usize) {
206        let col = fabric_port_index / self.num_ports_per_node / self.num_rows;
207        let row = (fabric_port_index / self.num_ports_per_node) % self.num_rows;
208        let port = fabric_port_index % self.num_ports_per_node;
209        (col, row, port)
210    }
211
212    #[must_use]
213    pub fn node_num_ingress_egress_ports(&self, col: usize, row: usize) -> usize {
214        node_num_ingress_egress_ports(
215            self.num_columns,
216            self.num_rows,
217            self.num_ports_per_node,
218            self.ports_per_node_limit,
219            col,
220            row,
221        )
222    }
223
224    #[must_use]
225    pub fn max_x(&self) -> usize {
226        self.num_columns - 1
227    }
228
229    #[must_use]
230    pub fn max_y(&self) -> usize {
231        self.num_rows - 1
232    }
233
234    #[must_use]
235    pub fn num_columns(&self) -> usize {
236        self.num_columns
237    }
238
239    #[must_use]
240    pub fn num_rows(&self) -> usize {
241        self.num_rows
242    }
243
244    #[must_use]
245    pub fn num_ports_per_node(&self) -> usize {
246        self.num_ports_per_node
247    }
248
249    #[must_use]
250    pub fn cycles_per_hop(&self) -> usize {
251        self.cycles_per_hop
252    }
253
254    #[must_use]
255    pub fn cycles_overhead(&self) -> usize {
256        self.cycles_overhead
257    }
258
259    #[must_use]
260    pub fn port_bits_per_tick(&self) -> usize {
261        self.port_bits_per_tick
262    }
263}
264
265pub mod functional;
266pub mod node;
267pub mod routed;
268
269#[test]
270fn port_index() {
271    let config: FabricConfig = FabricConfig::new(3, 4, 2, None, 1, 1, 1, 1, 1);
272
273    assert_eq!(config.col_row_port_to_fabric_port_index(0, 0, 0), 0);
274    assert_eq!(config.fabric_port_index_to_col_row_port(0), (0, 0, 0));
275
276    assert_eq!(config.col_row_port_to_fabric_port_index(0, 0, 1), 1);
277    assert_eq!(config.fabric_port_index_to_col_row_port(1), (0, 0, 1));
278
279    assert_eq!(config.col_row_port_to_fabric_port_index(0, 1, 0), 2);
280    assert_eq!(config.fabric_port_index_to_col_row_port(2), (0, 1, 0));
281
282    assert_eq!(config.col_row_port_to_fabric_port_index(0, 1, 1), 3);
283    assert_eq!(config.fabric_port_index_to_col_row_port(3), (0, 1, 1));
284
285    assert_eq!(config.col_row_port_to_fabric_port_index(1, 0, 0), 8);
286    assert_eq!(config.fabric_port_index_to_col_row_port(8), (1, 0, 0));
287
288    assert_eq!(config.col_row_port_to_fabric_port_index(1, 3, 0), 14);
289    assert_eq!(config.fabric_port_index_to_col_row_port(14), (1, 3, 0));
290
291    assert_eq!(config.col_row_port_to_fabric_port_index(2, 1, 1), 19);
292    assert_eq!(config.fabric_port_index_to_col_row_port(19), (2, 1, 1));
293}