use super::{InFlight, NetworkTopology, TopologyInfo, node_of, port_of};
use crate::sim::packet::coherence::{CoherenceMsg, MsgClass, Node};
use crate::sim::stats::Stats;
use crate::soc::coherence::Interconnect;
use crate::soc::coherence::stats::InterconnectStatPaths;
use std::cmp::Reverse;
use std::collections::{BinaryHeap, VecDeque};
#[derive(Clone, Copy, Debug)]
pub(super) struct Routed {
msg: CoherenceMsg,
to_node: usize,
arrived: u64,
}
#[derive(Debug)]
pub struct RoutedNetwork<T: NetworkTopology> {
topology: T,
cores: usize,
line_bytes: usize,
hop_latency: u64,
bytes_per_cycle: usize,
queues: Vec<Vec<[VecDeque<Routed>; 4]>>,
link_free_at: Vec<Vec<[u64; 4]>>,
in_flight: BinaryHeap<Reverse<InFlight>>,
next_seq: u64,
stat_paths: InterconnectStatPaths,
}
impl<T: NetworkTopology> RoutedNetwork<T> {
#[must_use]
pub fn new(
topology: T,
cores: usize,
line_bytes: usize,
hop_latency: u64,
bytes_per_cycle: usize,
stat_paths: InterconnectStatPaths,
) -> Self {
let nodes = topology.node_count();
assert!(nodes > cores, "topology must have a node per core plus the home");
let queues = (0..nodes)
.map(|node| {
(0..topology.neighbours(node).len())
.map(|_| std::array::from_fn(|_| VecDeque::new()))
.collect()
})
.collect();
let link_free_at =
(0..nodes).map(|node| vec![[0; 4]; topology.neighbours(node).len()]).collect();
Self {
topology,
cores,
line_bytes,
hop_latency,
bytes_per_cycle: bytes_per_cycle.max(1),
queues,
link_free_at,
in_flight: BinaryHeap::new(),
next_seq: 0,
stat_paths,
}
}
const fn transfer_cycles(&self, msg: CoherenceMsg) -> u64 {
(msg.bytes(self.line_bytes).div_ceil(self.bytes_per_cycle)) as u64
}
fn enqueue(&mut self, node: usize, routed: Routed) {
let next = self.topology.next_hop(node, routed.to_node);
let link =
self.topology.neighbours(node).iter().position(|n| *n == next).unwrap_or_default();
self.queues[node][link][routed.msg.class().index()].push_back(routed);
}
fn forward(&mut self, now: u64, stats: &mut Stats) {
for node in 0..self.queues.len() {
let neighbours = self.topology.neighbours(node);
for (link, &next) in neighbours.iter().enumerate() {
for class in MsgClass::ALL {
let c = class.index();
if self.link_free_at[node][link][c] > now {
continue;
}
let Some(routed) = self.queues[node][link][c].pop_front() else { continue };
if routed.arrived < now {
stats.counter(self.stat_paths.blocked_cycles).add(now - routed.arrived);
}
let transfer = self.transfer_cycles(routed.msg);
self.link_free_at[node][link][c] = now + transfer;
stats.counter(self.stat_paths.busy_cycles).add(transfer);
let seq = self.next_seq;
self.next_seq += 1;
self.in_flight.push(Reverse(InFlight {
deliver_at: now + self.hop_latency + transfer,
seq,
to: node_of(next, self.cores),
msg: routed.msg,
}));
}
}
}
}
}
impl<T: NetworkTopology> Interconnect for RoutedNetwork<T> {
fn send(&mut self, now: u64, from: Node, msg: CoherenceMsg) {
let to_node = port_of(msg.destination(), self.cores);
let from_node = port_of(from, self.cores);
self.enqueue(from_node, Routed { msg, to_node, arrived: now });
}
fn tick(&mut self, now: u64, stats: &mut Stats, deliver: &mut dyn FnMut(Node, CoherenceMsg)) {
while let Some(Reverse(next)) = self.in_flight.peek() {
if next.deliver_at > now {
break;
}
let Some(Reverse(flight)) = self.in_flight.pop() else { break };
let at_node = port_of(flight.to, self.cores);
let to_node = port_of(flight.msg.destination(), self.cores);
if at_node == to_node {
stats.counter(self.stat_paths.messages).inc();
stats.counter(self.stat_paths.bytes).add(flight.msg.bytes(self.line_bytes) as u64);
deliver(flight.msg.destination(), flight.msg);
} else {
self.enqueue(at_node, Routed { msg: flight.msg, to_node, arrived: now });
}
}
self.forward(now, stats);
}
fn is_idle(&self) -> bool {
self.in_flight.is_empty()
&& self
.queues
.iter()
.all(|links| links.iter().all(|qs| qs.iter().all(VecDeque::is_empty)))
}
fn topology(&self) -> TopologyInfo {
TopologyInfo {
kind: self.topology.kind(),
endpoints: self.cores + 1,
nodes: self.topology.node_count(),
diameter: self.topology.diameter(),
}
}
}
#[cfg(test)]
mod routed_tests {
use super::*;
use crate::common::CoreId;
use crate::common::{LineAddr, PhysAddr};
use crate::sim::components::ReqId;
use crate::sim::packet::coherence::ReqKind;
use crate::soc::coherence::interconnect::{Hypercube, Mesh2D, Ring};
fn req(core: u32) -> CoherenceMsg {
CoherenceMsg::Req {
txn: ReqId::new(u64::from(core)),
line: LineAddr::from_phys(PhysAddr::new(0x1000), 64),
kind: ReqKind::ReadShared,
requester: CoreId::new(core),
}
}
fn deliver_all(net: &mut dyn Interconnect, from: u64, to: u64) -> Vec<(u64, Node)> {
let mut stats = Stats::new();
let mut out = Vec::new();
for now in from..=to {
net.tick(now, &mut stats, &mut |node, _| out.push((now, node)));
}
out
}
#[test]
fn ring_routes_take_the_shorter_direction() {
let ring = Ring::new(6);
assert_eq!(ring.next_hop(0, 2), 1);
assert_eq!(ring.next_hop(0, 5), 5);
assert_eq!(ring.next_hop(0, 3), 1, "tie goes forward");
assert_eq!(ring.diameter(), 3);
}
#[test]
fn mesh_routes_x_first_and_torus_wraps() {
let mesh = Mesh2D::for_endpoints(5, false);
assert_eq!(mesh.node_count(), 9);
assert_eq!(mesh.next_hop(0, 8), 1, "move along x first");
assert_eq!(mesh.next_hop(2, 8), 5, "then along y");
assert_eq!(mesh.neighbours(4), vec![1, 3, 5, 7]);
let torus = Mesh2D::for_endpoints(5, true);
assert_eq!(torus.next_hop(0, 2), 2, "wraparound is one hop");
assert_eq!(torus.diameter(), 2);
}
#[test]
fn hypercube_flips_one_dimension_per_hop() {
let cube = Hypercube::for_endpoints(5);
assert_eq!(cube.node_count(), 8);
assert_eq!(cube.neighbours(0), vec![1, 2, 4]);
assert_eq!(cube.next_hop(0, 7), 1);
assert_eq!(cube.next_hop(1, 7), 3);
assert_eq!(cube.next_hop(3, 7), 7);
assert_eq!(cube.diameter(), 3);
}
#[test]
fn a_message_pays_hop_latency_per_hop() {
let mut net =
RoutedNetwork::new(Ring::new(6), 4, 64, 2, 8, InterconnectStatPaths::new("t"));
net.send(0, Node::Core(CoreId::new(0)), req(0));
let delivered = deliver_all(&mut net, 0, 40);
assert_eq!(delivered, vec![(6, Node::Home)]);
assert!(net.is_idle());
}
#[test]
fn a_busy_link_serialises_same_class_messages() {
let mut net =
RoutedNetwork::new(Ring::new(3), 2, 64, 1, 8, InterconnectStatPaths::new("t"));
net.send(0, Node::Core(CoreId::new(0)), req(0));
net.send(0, Node::Core(CoreId::new(0)), req(0));
let delivered = deliver_all(&mut net, 0, 20);
assert_eq!(delivered.iter().map(|d| d.0).collect::<Vec<_>>(), vec![2, 3]);
}
}