sim_fabric/
access_gen.rs

1// Copyright (c) 2025 Graphcore Ltd. All rights reserved.
2
3use std::fmt;
4use std::rc::Rc;
5
6use clap::ValueEnum;
7use gwr_engine::types::AccessType;
8use gwr_model_builder::EntityGet;
9use gwr_models::fabric::FabricConfig;
10use gwr_models::memory::memory_access::MemoryAccess;
11use gwr_models::memory::memory_map::DeviceId;
12use gwr_track::entity::Entity;
13use rand::SeedableRng;
14use rand::seq::{IteratorRandom, SliceRandom};
15use rand_xoshiro::Xoshiro256PlusPlus;
16use serde::Serialize;
17
18#[derive(ValueEnum, Clone, Copy, Default, Debug, Serialize, PartialEq)]
19#[serde(rename_all = "kebab-case")]
20pub enum TrafficPattern {
21    /// All sources will send to one dest chosen at random
22    #[default]
23    AllToOne,
24
25    /// All sources will send to random (valid) destinations
26    Random,
27
28    /// All sources will be allocated a fixed (random) destination
29    AllToAllFixed,
30
31    /// All sources will send to to all other destinations in sequence
32    AllToAllSeq,
33
34    /// All sources will send to to all other destinations in random (but
35    /// repeated) order
36    AllToAllRandom,
37}
38
39impl fmt::Display for TrafficPattern {
40    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41        write!(f, "{self:?}")
42    }
43}
44
45/// A memory access generator that can be used by the `Source` to produce
46/// accesses on the fly.
47///
48/// This allows each access being created to be unique which aids debug of the
49/// system.
50#[derive(EntityGet)]
51pub struct AccessGen {
52    entity: Rc<Entity>,
53    config: Rc<FabricConfig>,
54    source_index: usize,
55    dest_index: usize,
56    traffic_pattern: TrafficPattern,
57    overhead_size_bytes: usize,
58    payload_size_bytes: usize,
59    num_send_frames: usize,
60    num_sent_frames: usize,
61    rng: Xoshiro256PlusPlus,
62    next_dests: Vec<usize>,
63}
64
65impl AccessGen {
66    #[expect(clippy::too_many_arguments)]
67    #[must_use]
68    pub fn new(
69        parent: &Rc<Entity>,
70        config: Rc<FabricConfig>,
71        source_index: usize,
72        initial_dest_index: usize,
73        traffic_pattern: TrafficPattern,
74        overhead_size_bytes: usize,
75        payload_size_bytes: usize,
76        num_send_frames: usize,
77        seed: u64,
78    ) -> Self {
79        // Create a local RNG which is different per source
80        let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed ^ (source_index as u64));
81        let num_ports = config.num_ports();
82
83        let dest_index = match traffic_pattern {
84            TrafficPattern::Random => (0..num_ports).choose(&mut rng).unwrap(),
85            TrafficPattern::AllToOne
86            | TrafficPattern::AllToAllFixed
87            | TrafficPattern::AllToAllSeq
88            | TrafficPattern::AllToAllRandom => initial_dest_index,
89        };
90
91        let mut next_dests: Vec<usize> = (0..num_ports).collect();
92        next_dests.shuffle(&mut rng);
93
94        Self {
95            entity: Rc::new(Entity::new(parent, &format!("gen{source_index}"))),
96            config,
97            source_index,
98            dest_index,
99            traffic_pattern,
100            overhead_size_bytes,
101            payload_size_bytes,
102            num_send_frames,
103            num_sent_frames: 0,
104            rng,
105            next_dests,
106        }
107    }
108
109    #[must_use]
110    fn next_dest(&mut self) -> usize {
111        let num_ports = self.config.max_num_ports();
112        match self.traffic_pattern {
113            TrafficPattern::Random => (0..num_ports).choose(&mut self.rng).unwrap(),
114            TrafficPattern::AllToOne => self.dest_index,
115            TrafficPattern::AllToAllFixed => self.dest_index,
116            TrafficPattern::AllToAllSeq => (self.dest_index + 1) % num_ports,
117            TrafficPattern::AllToAllRandom => {
118                let dest = self.next_dests.pop().unwrap();
119                if self.next_dests.is_empty() {
120                    self.next_dests = (0..num_ports).collect();
121                    self.next_dests.shuffle(&mut self.rng);
122                }
123                dest
124            }
125        }
126    }
127}
128
129impl Iterator for AccessGen {
130    type Item = MemoryAccess;
131    fn next(&mut self) -> Option<Self::Item> {
132        // If this can only send to self then there is nothing to do.
133        let no_valid_accesses = (self.dest_index == self.source_index)
134            && (self.traffic_pattern == TrafficPattern::AllToOne
135                || self.traffic_pattern == TrafficPattern::AllToAllFixed);
136
137        if no_valid_accesses {
138            return None;
139        }
140
141        if self.num_sent_frames < self.num_send_frames {
142            while self.dest_index == self.source_index {
143                self.dest_index = self.next_dest();
144            }
145
146            let label = (self.num_sent_frames | (self.source_index << 32)) as u64;
147            self.num_sent_frames += 1;
148
149            // Route to the correct device, but keep the access label in the
150            // destination address to aid debug.
151            let dest = self.config.port_indices()[self.dest_index];
152            let access = Some(MemoryAccess::new(
153                &self.entity,
154                AccessType::WriteRequest,
155                self.payload_size_bytes,
156                label,
157                self.source_index as u64,
158                DeviceId(dest as u64),
159                DeviceId(self.source_index as u64),
160                self.overhead_size_bytes,
161            ));
162
163            self.dest_index = self.next_dest();
164            access
165        } else {
166            None
167        }
168    }
169}