Mordred is a modular, packet-switched Network-on-Chip (NoC) simulation library for the Structural Simulation Toolkit (SST). It provides a router, endpoint NIC, and topology implementations as drop-in alternatives to Merlin, with a pluggable physical-channel layer that allows the transport between routers and endpoints to be swapped independently of the routing and flow-control machinery.
An introductory presentation from the 2025 SST User's Group meeting is available here.
- CMake >= 3.19
- SST and SST-Elements (including Merlin) >= 15.0
- C++17 compiler
sstandsst-configmust be onPATH- Prydwen must be built and registered before building Mordred (Mordred's build
system references
prydwen/src/for thePhysChannelAPIheader)- Mordred can be built without Prydwen; add the
-DMORDRED_ENABLE_PHYS_CHANNEL=OFFto your cmake command line when building mordred.
- Mordred can be built without Prydwen; add the
Build prydwen first, then mordred:
# 1. Build prydwen (physical channel library)
cd ../prydwen
mkdir build && cd build
cmake ..
make -j
make install
# 2. Build mordred
cd ../../mordred
mkdir build && cd build
cmake ..
make -j
make install
make testsrc/ Main library source
tests/ SST Python test scripts
repotest/ Sandbox scripts for experiments and performance comparisons
merlin_testing/ Scripts comparing Mordred and Merlin behavior
sst_test_framework/ Files for running tests via the standard SST test framework
This folder is a sandbox for scripts and tests under development, performance comparisons, etc. No promises are made as to completeness or correctness. Most scripts are copy/edit variants of scripts from tests/.
Two historical notes:
- An early component
mordred.test_epserved as a stand-in formerlin.test_nic. It has since been removed/replaced, so some older scripts in this folder may fail. - Several scripts reference
merlin.clocked_offered_load, a local (non-upstreamed) component based onmerlin.offered_loadthat generated traffic using a clock rate rather than a bandwidth parameter. Its behavior was equivalent tomerlin.offered_loadwhenlink_bwwas set to match the Mordred network's link bandwidth.
Contains files for executing the tests via the standard SST elements test framework. Most of the documentation for this approach can be found here.
The top-level SST component representing one router in the network. It owns the topology, per-port control subcomponents, a VC allocator, and a crossbar arbiter.
Parameters
| Parameter | Description | Default |
|---|---|---|
id |
Router ID (must follow topology numbering) | — |
clock |
Clock frequency (e.g. "1GHz") |
— |
num_ports |
Total number of ports (routing + local combined) | — |
num_local_ports |
Number of ports connected to endpoints | — |
num_vns |
Number of virtual networks | — |
num_vcs |
Virtual channels per VN | — |
flit_size |
Flit width (e.g. "16b", "64b") |
— |
input_buf_size |
Per-VC input buffer size (e.g. "8" flits) |
— |
output_buf_size |
Per-VC output buffer size | — |
verbose |
Verbosity level | 0 |
Subcomponent slots
| Slot | API | Required |
|---|---|---|
topology |
TopologyAPI |
Yes |
portcontrol* |
RtrPortControlAPI |
Yes |
vcalloc |
VcAllocAPI |
Yes |
xbarArb |
XbarArbAPI |
Yes |
An SST::Interfaces::SimpleNetwork subcomponent that connects an endpoint (e.g.
merlin.test_nic, memHierarchy.MemNIC) to a Mordred router over a raw SST link.
Handles packet-to-flit fragmentation, credit-based flow control, and reassembly.
Minimum packet size is 2 flits (one HEAD flit carrying the destination and a TAIL flit carrying the payload).
Parameters
| Parameter | Description |
|---|---|
clock |
Interface clock frequency |
input_buf_size |
Input buffer size in bits/bytes (SI prefix supported) |
output_buf_size |
Output buffer size |
verbose |
Verbosity level |
Statistics: packets_recv, average_noc_latency, average_packet_size
Identical external interface to mordredNIC but uses an inner PhysChannelAPI
subcomponent (slot port_iface) instead of a raw SST link. This makes the physical
transport between the NIC and router pluggable — swap in prydwen.genericPhysChannel
for a simple raw-link transport, or prydwen.uciePhysChannel (from the prydwen repo)
for a full UCIe adapter-layer transport.
Additional subcomponent slot
| Slot | API | Description |
|---|---|---|
port_iface |
PhysChannelAPI |
Physical channel to the router (e.g. prydwen.genericPhysChannel) |
A configurable traffic-generator component for mordred test scenarios that require
num_vns > 1. merlin.test_nic and merlin.trafficgen both hardcode num_vns=1
when loading their networkIF subcomponent, making them incompatible with
prydwen.uciePhysChannel in num_stacks=2 configuration. mordredTestEP accepts
num_vns as an explicit parameter and correctly propagates it to the networkIF loader.
Each endpoint sends num_messages messages, rotating through VNs 0..num_vns-1 and
cycling through destinations, then calls primaryComponentOKToEndSim().
Parameters
| Parameter | Description | Default |
|---|---|---|
id |
Endpoint ID | 0 |
num_peers |
Total endpoints in the network | 1 |
num_vns |
VNs to request from the networkIF subcomponent |
1 |
num_messages |
Total messages to send | 10 |
message_size |
Message size (UnitAlgebra; bits or bytes) | 64b |
clock |
Clock frequency | 1GHz |
Subcomponent slot: networkIF — accepts any SST::Interfaces::SimpleNetwork implementation.
Per-port subcomponent of mordred_router. Manages the raw SST link to the attached
router or endpoint, input/output VC state machines, credit tracking, and flit
buffering. Coordinates with the VC allocator and crossbar arbiter through shared
per-router data structures.
Same as rtrPortControl but uses a PhysChannelAPI subcomponent (slot port_iface)
for the physical link, matching mordredNicPC on the endpoint side.
Moved to the prydwen repo. Concrete implementations and the abstract API now live in
prydwen/src/. Mordred referencesprydwen/src/at build time viaPRYDWEN_SRC_DIRinsrc/CMakeLists.txt.
Available implementations (registered in the prydwen element library):
| SST name | Library | Description |
|---|---|---|
prydwen.genericPhysChannel |
prydwen | Raw SST::Link transport via PhysChannelLinkEvent |
prydwen.uciePhysChannel |
prydwen | Full UCIe adapter-layer with FLIT serialization + credits |
All topologies are subcomponents registered under the TopologyAPI. Endpoints must be
connected to local ports only — do not connect endpoints to routing ports.
All routers in a simulation should have the same number of local ports. Unconnected local ports are permitted.
Rectangular grid without wrap-around.
Router ID = (y × xDim) + x, where (x=0, y=0) is the bottom-left corner.
Parameters: xDim, yDim, num_local_ports, num_vns, num_vcs
Mesh with wrap-around in both dimensions. Requires at least 2 VCs per VN to avoid deadlock on larger networks.
Router ID = (y × xDim) + x
Parameters: xDim, yDim, num_local_ports, num_vns, num_vcs
Extension of the 2D torus with a Z dimension.
Router ID = (z × (xDim × yDim)) + (y × xDim) + x
Parameters: xDim, yDim, zDim, num_local_ports, num_vns, num_vcs
K-ary N-fly topology. A helper class in tests/flatbutterfly_k2n4_testnic.py handles
router and endpoint naming/numbering.
Parameters: k, n, num_local_ports, num_vns, num_vcs
| Component | SST name | Description |
|---|---|---|
VcAllocRR |
mordred.vcAllocRR |
Round-robin virtual channel allocator |
XbarArbRR |
mordred.xbarArbRR |
Round-robin crossbar arbitration |
The topology defines the number of VCs per VN, so VC count is a topology-level property, not a router-level one. Inside the router, the total VC count (num_vcs) is the sum across all VNs. Most data structures are multi-dimensional arrays contained within a port (or a per-port object) where one dimension is the number of VNs and another is the number of VCs.
The router owns a vector perPortSharedObjs (one element per port), where each element is a RtrOwnedSharedObjs (defined in MordredEvents). RtrOwnedSharedObjs contains a pair of 2D vectors: needVcAlloc and needSwitchAlloc.
On each clock tick, RtrPortControl inspects the state of each (VN, VC) pair it owns. If the flit in a pair needs an output VC, needVcAlloc for that pair is marked. The VC allocator can then operate on any/all pairs that need allocation. When it grants an output VC, it clears needVcAlloc for that pair. This allows requests to persist across clock cycles, and lets the VC allocator reason about all pending requests simultaneously (across ports, VNs, and VCs).
The same pattern applies for switch allocation via needSwitchAlloc.
Currently MordredRouter performs switch allocation on a per-packet basis, but the design should support per-clock-tick allocation (this should be tested).
Credits are exchanged during SST's init() phases before simulation begins. One credit
represents one flit of buffer space. The initialization handshake runs strictly between
each MordredNIC (or MordredNicPC) and the RtrPortControl (or RtrPortControlPC)
of the directly connected router port; no global broadcast occurs.
| Phase | MordredNIC | RtrPortControl |
|---|---|---|
| 0 | Send: report endpoint | Send: report router, router ID, port number |
| 1 | Recv: router ID and port number | Recv: connection type; if endpoint, send num VNs, num VCs, flit width |
| 2 | Recv: num VNs, num VCs, flit width | If endpoint: send endpoint ID |
| 3 | Recv: endpoint ID | Idle |
| 4 | Exchange credits | Exchange credits |
| 5+ | Receive credits; enqueue other events | Receive credits; enqueue other events |
mordred.mordredNIC fills the SST::Interfaces::SimpleNetwork subcomponent slot, so
it works with any component that accepts a SimpleNetwork interface:
merlin.test_nic— general-purpose test endpointmemHierarchy.MemNIC— seetests/ipdps25tutorial_demo7.pymemHierarchy.MemNICFour— seetests/mordred_memNICFour.pymerlin.Bridge— seetests/mordred_testBridge.py
To replace a merlin.hr_router with a single-router deployment, use a 1×1 mesh
topology. Links that previously connected to hr_router become links to the local
ports of mordred.mordred_router.
Tests live in tests/ and are run via make test from the build directory.
Standard tests (always registered):
| Test script | Topology | Notes |
|---|---|---|
mesh3x3_testnic.py |
3×3 mesh | Baseline: 1 VC, 1 VN, 16-bit flits, 1 local port per router |
mesh3x3_untimed_broadcast.py |
3×3 mesh | send_untimed_broadcast=true; exercises MeshTopology::routeUntimedBroadcastPacket |
mesh3x3_2local_testnic.py |
3×3 mesh | Concentration=2 (2 local ports per router); exercises non-zero dest - endptZeroId final-hop offset |
mesh3x3_single_flit.py |
3×3 mesh | 1-flit request → 2-flit minimum packet; HEAD immediately followed by TAIL, empty body loop |
mesh3x3_large_message.py |
3×3 mesh | 16-flit packets; stresses body-flit send loop and output-buffer backpressure |
torus5x5_2vc_testnic.py |
5×5 2D torus | 2 VCs required for deadlock avoidance on 5×5 |
torus5x5_2vc_untimed_broadcast.py |
5×5 2D torus | send_untimed_broadcast=true; exercises TorusTopo::sendBroadcast wrap-aware flood logic |
torus5x5_3vc_testnic.py |
5×5 2D torus | 3 VCs; exercises VcAllocRR beyond the minimum deadlock-free VC count |
torus3D_3x3x3_2vc_testnic.py |
3×3×3 3D torus | 2 VCs |
torus3D_3x3x3_2vc_untimed_broadcast.py |
3×3×3 3D torus | send_untimed_broadcast=true; exercises Torus3DTopo::sendBroadcast across 6 directions |
flatbutterfly_k2n4_testnic.py |
k=2, n=4 butterfly | — |
ipdps25tutorial_demo7.py |
Mesh | memHierarchy.MemNIC integration |
mordred_memNICFour.py |
Mesh | memHierarchy.MemNICFour integration |
mordred_testBridge.py |
Mesh | merlin.Bridge integration |
PhysChannel tests (registered only when MORDRED_ENABLE_PHYS_CHANNEL=ON; require prydwen):
| Test script | Topology | Transport |
|---|---|---|
mesh2x1_rtrportcontrolpc.py |
2×1 mesh | prydwen.genericPhysChannel |
mesh2x1_uciePhysChannel.py |
2×1 mesh | prydwen.uciePhysChannel |
mesh3x3_uciePhysChannel.py |
3×3 mesh | prydwen.uciePhysChannel |
flatbutterfly_k2n4_uciePhysChannel.py |
k=2, n=4 butterfly | prydwen.uciePhysChannel |
torus5x5_2vc_uciePhysChannel.py |
5×5 2D torus | prydwen.uciePhysChannel, 2 VCs |
torus5x5_3vc_uciePhysChannel.py |
5×5 2D torus | prydwen.uciePhysChannel, 3 VCs |
torus5x5_4vc_uciePhysChannel.py |
5×5 2D torus | prydwen.uciePhysChannel, 4 VCs |
torus3D_3x3x3_2vc_uciePhysChannel.py |
3×3×3 3D torus | prydwen.uciePhysChannel, 2 VCs |
mesh2x1_uciePhysChannel_flit_format2.py |
2×1 mesh | prydwen.uciePhysChannel, FLIT format 2 (68B/64B payload, 16 GT/s) |
mesh3x3_uciePhysChannel_2module.py |
3×3 mesh | prydwen.uciePhysChannel, 2 bonded modules |
mesh3x3_uciePhysChannel_2stack.py |
3×3 mesh | prydwen.uciePhysChannel, 2 UCIe stacks; uses mordred.mordredTestEP for multi-VN traffic |
- Priority: Not implemented. Use VNs for traffic class separation (SST
SimpleNetwork::Requesthas no priority field). - Router latency: Fixed; no configurable pipeline depth.
- Packet size: No maximum enforced. Minimum is 2 flits (one HEAD + one TAIL), enforced at the NIC.
- Channel width: Only one flit traverses a link per clock cycle. The
channel_widthbranch has initial support for modifying this but is likely out of date. - NetworkInspectors: Not yet supported.
- Buffer allocation: Buffers are fully segregated per (VN, VC) pair; no shared pool. The output buffer currently has a small buffer per (VN, VC). A configurable arbitration policy for which (VN, VC) gets access is not yet implemented; the current design is round-robin. See also the
OutputArbitrationAPI class inRtrPortControlAPI.h. - Arbitration: Round-robin only for both VC allocation and crossbar arbitration.
- Timing: Router and subcomponent timing should continue to be reviewed.
- Routing: Each topology implements its own routing; no general-purpose routing algorithm API.
- Broadcast/Multicast: Not supported at simulation time. Untimed broadcast (SST init/complete phases) is supported and tested via the
*_untimed_broadcasttest scripts — each topology implementsrouteUntimedBroadcastPacketwith its own flood logic. - UCIe multi-stack (requires Prydwen) (
num_stacks=2): Tested viamesh3x3_uciePhysChannel_2stack.pyusingmordred.mordredTestEPwithnum_vns=2.merlin.test_nicandmerlin.trafficgencannot be used here: both hardcodenum_vns=1when loading theirnetworkIFsubcomponent (trafficgen'snum_vnsparameter is documented but its value is ignored in the implementation), souciePhysChannelalways receives a VN count of 1 and rejectsnum_vns_per_stack="1,1"(total=2) as a mismatch.mordredTestEPwas added to mordred specifically to support configurablenum_vns.
This work was supported by the U.S. Department of Energy, Office of Science, Advanced Scientific Computing Research program under project 84245 — Democratization of Co-design for Energy-Efficient Heterogeneous Computing (DeCoDe) at Pacific Northwest National Laboratory (PNNL). PNNL is a multi-program national laboratory operated for the U.S. Department of Energy (DOE) by Battelle Memorial Institute under Contract No. DE-AC05-76RL01830.