Repository: tenstorrent/tt-metal
Pull request #22124: #21161: Integrate Dynamic Routing with 2D Push Fabric
Author: login: tt-asaigal
Created: 2025-05-15T00:23:05Z | Merged: 2025-05-16T04:30:47Z

### Ticket
https://github.com/tenstorrent/tt-metal/issues/21161

### Problem description
- 2D Push Fabric does not currently support Dynamic Routing, i.e. routing tables are written to ERISC cores but not used
- We will rely on Routing Tables for Inter-Mesh Routing, hence this feature needs to be integrated with `fabric_erisc_datamover`

### What's changed
- Add `FabricConfig::FABRIC_2D_DYNAMIC` to allow users to enable Dynamic Routing with 2D Push Fabric
- Add `MeshPacketHeader`, which contains dest and Mcast information, programmed by the user through `fabric_set_unicast_route` and `fabric_set_mcast_route`
- Add `port_direction_table` data-structure, populated when the EDM kernel is started. This data structure contains the mapping between an ethernet port/channel and its corresponding direction
- Modify `run_receiver_channel_step` in `tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp`. Specifically, add:
  - `can_forward_packet_completely` and `receiver_forward_packet` for `MeshPacketHeader`
  - Support for querying the Intra-Mesh and Inter-Mesh routing tables when forwarding packets to downstream chips
  - Support for each receiver to forward incoming packets to more than one downstream sender, for Mcasts

**Pending: Perf Investigation and some minor cleanup**

### Checklist
- [x] [All post commit](https://github.com/tenstorrent/tt-metal/actions/workflows/all-post-commit-workflows.yaml) CI passes https://github.com/tenstorrent/tt-metal/actions/runs/15035015173
- [ ] [Blackhole Post commit](https://github.com/tenstorrent/tt-metal/actions/workflows/blackhole-post-commit.yaml) CI with demo tests passes (if applicable)
- [ ] [Model regression](https://github.com/tenstorrent/tt-metal/actions/workflows/perf-models.yaml) CI passes (if applicable)
- [ ] [Device performance regression](https://github.com/tenstorrent/tt-metal/actions/workflows/perf-device-models.yaml) CI passes (if applicable)
- [ ] **(For models and ops writers)** Full [new models tests](https://github.com/tenstorrent/tt-metal/actions/workflows/full-new-models-suite.yaml) CI passes (if applicable)
- [x] New/Existing tests provide coverage for changes


Diff:
diff --git a/tests/tt_metal/tt_fabric/common/fabric_fixture.hpp b/tests/tt_metal/tt_fabric/common/fabric_fixture.hpp
index 0d2dcbdb1854..3ff435dfd2d3 100644
--- a/tests/tt_metal/tt_fabric/common/fabric_fixture.hpp
+++ b/tests/tt_metal/tt_fabric/common/fabric_fixture.hpp
@@ -96,6 +96,15 @@ class Fabric2DPushFixture : public BaseFabricFixture {
     void SetUp() override { this->SetUpDevices(tt::tt_metal::FabricConfig::FABRIC_2D_PUSH); }
 };
 
+class Fabric2DDynamicFixture : public BaseFabricFixture {
+    void SetUp() override { this->SetUpDevices(tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC); }
+};
+
+struct McastRoutingInfo {
+    RoutingDirection mcast_dir;
+    uint32_t num_mcast_hops;
+};
+
 void RunTestUnicastRaw(
     BaseFabricFixture* fixture, uint32_t num_hops = 1, RoutingDirection direction = RoutingDirection::E);
 
@@ -104,13 +113,17 @@ void RunTestUnicastConnAPI(
 
 void RunTestMCastConnAPI(BaseFabricFixture* fixture);
 
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info);
+
 bool find_device_with_neighbor_in_multi_direction(
     BaseFabricFixture* fixture,
     std::pair<mesh_id_t, chip_id_t>& src_mesh_chip_id,
     std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>& dst_mesh_chip_ids_by_dir,
     chip_id_t& src_physical_device_id,
     std::unordered_map<RoutingDirection, std::vector<chip_id_t>>& dst_physical_device_ids_by_dir,
-    const std::unordered_map<RoutingDirection, uint32_t>& mcast_hops);
+    const std::unordered_map<RoutingDirection, uint32_t>& mcast_hops,
+    std::optional<RoutingDirection> incoming_direction = std::nullopt);
 
 bool find_device_with_neighbor_in_direction(
     BaseFabricFixture* fixture,
diff --git a/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp b/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
index 2c73d011b540..dc62319fe4aa 100644
--- a/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
+++ b/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
@@ -18,6 +18,7 @@
 #include <vector>
 #include <random>
 
+#include <tt-metalium/allocator.hpp>
 #include <tt-metalium/core_coord.hpp>
 #include <tt-metalium/data_types.hpp>
 #include <tt-metalium/device.hpp>
@@ -41,6 +42,38 @@ namespace fabric_router_tests {
 std::random_device rd;  // Non-deterministic seed source
 std::mt19937 global_rng(rd());
 
+struct WorkerMemMap {
+    uint32_t packet_header_address;
+    uint32_t source_l1_buffer_address;
+    uint32_t packet_payload_size_bytes;
+    uint32_t test_results_address;
+    uint32_t target_address;
+    uint32_t test_results_size_bytes;
+};
+
+// Utility function reused across tests to get address params
+WorkerMemMap generate_worker_mem_map(tt_metal::IDevice* device, Topology topology) {
+    constexpr uint32_t PACKET_HEADER_RESERVED_BYTES = 45056;
+    constexpr uint32_t DATA_SPACE_RESERVED_BYTES = 851968;
+    constexpr uint32_t TEST_RESULTS_SIZE_BYTES = 128;
+
+    uint32_t base_addr = device->allocator()->get_base_allocator_addr(tt_metal::HalMemType::L1);
+    uint32_t packet_header_address = base_addr;
+    uint32_t source_l1_buffer_address = base_addr + PACKET_HEADER_RESERVED_BYTES;
+    uint32_t test_results_address = source_l1_buffer_address + DATA_SPACE_RESERVED_BYTES;
+    uint32_t target_address = source_l1_buffer_address;
+
+    uint32_t packet_payload_size_bytes = (topology == Topology::Mesh) ? 2048 : 4096;
+
+    return {
+        packet_header_address,
+        source_l1_buffer_address,
+        packet_payload_size_bytes,
+        test_results_address,
+        target_address,
+        TEST_RESULTS_SIZE_BYTES};
+}
+
 std::vector<uint32_t> get_random_numbers_from_range(uint32_t start, uint32_t end, uint32_t count) {
     std::vector<uint32_t> range(end - start + 1);
 
@@ -85,7 +118,8 @@ bool find_device_with_neighbor_in_multi_direction(
     std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>& dst_mesh_chip_ids_by_dir,
     chip_id_t& src_physical_device_id,
     std::unordered_map<RoutingDirection, std::vector<chip_id_t>>& dst_physical_device_ids_by_dir,
-    const std::unordered_map<RoutingDirection, uint32_t>& mcast_hops) {
+    const std::unordered_map<RoutingDirection, uint32_t>& mcast_hops,
+    std::optional<RoutingDirection> incoming_direction) {
     auto control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
 
     auto devices = fixture->get_devices();
@@ -93,6 +127,15 @@ bool find_device_with_neighbor_in_multi_direction(
     bool connection_found = false;
     for (auto* device : devices) {
         src_mesh_chip_id = control_plane->get_mesh_chip_id_from_physical_chip_id(device->id());
+        if (incoming_direction.has_value()) {
+            if (!control_plane
+                     ->get_intra_chip_neighbors(
+                         src_mesh_chip_id.first, src_mesh_chip_id.second, incoming_direction.value())
+                     .size()) {
+                // This potential source will not have the requested incoming direction, skip
+                continue;
+            }
+        }
         std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
             temp_end_mesh_chip_ids_by_dir;
         std::unordered_map<RoutingDirection, std::vector<chip_id_t>> temp_physical_end_device_ids_by_dir;
@@ -131,6 +174,214 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    bool mcast_group_found = find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+
+    if (!mcast_group_found) {
+        GTEST_SKIP() << "Mcast group not found for line mcast test";
+    }
+
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto& fabric_context = control_plane->get_fabric_context();
+    const auto topology = fabric_context.get_fabric_topology();
+    const auto& edm_config = fabric_context.get_fabric_router_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    auto worker_mem_map = generate_worker_mem_map(sender_device, edm_config.topology);
+    uint32_t num_packets = 100;
+    uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
+
+    // common compile time args for sender and receiver
+    std::vector<uint32_t> compile_time_args = {
+        worker_mem_map.test_results_address, worker_mem_map.test_results_size_bytes, worker_mem_map.target_address};
+
+    std::map<string, string> defines = {};
+    if (is_2d_fabric) {
+        defines["FABRIC_2D"] = "";
+    }
+
+    auto sender_program = tt_metal::CreateProgram();
+    auto sender_kernel = tt_metal::CreateKernel(
+        sender_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp",
+        {sender_logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args,
+            .defines = defines});
+
+    std::vector<uint32_t> sender_runtime_args = {
+        worker_mem_map.packet_header_address,
+        worker_mem_map.source_l1_buffer_address,
+        worker_mem_map.packet_payload_size_bytes,
+        num_packets,
+        receiver_noc_encoding,
+        time_seed,
+        mcast_start_id.second,
+        mcast_start_id.first};
+
+    std::vector<uint32_t> mcast_header_rtas(4, 0);
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_header_rtas[static_cast<uint32_t>(
+            control_plane->routing_direction_to_eth_direction(routing_info.mcast_dir))] = routing_info.num_mcast_hops;
+    }
+    sender_runtime_args.insert(sender_runtime_args.end(), mcast_header_rtas.begin(), mcast_header_rtas.end());
+    // append the EDM connection rt args
+    append_fabric_connection_rt_args(
+        sender_phys_id, mcast_start_phys_id, 0, sender_program, {sender_logical_core}, sender_runtime_args);
+
+    tt_metal::SetRuntimeArgs(sender_program, sender_kernel, sender_logical_core, sender_runtime_args);
+
+    // Create the receiver programs for validation on all devices involved in the Mcast
+    std::vector<uint32_t> receiver_runtime_args = {worker_mem_map.packet_payload_size_bytes, num_packets, time_seed};
+    std::unordered_map<tt_metal::IDevice*, std::shared_ptr<tt_metal::Program>> recv_programs;
+    recv_programs[mcast_start_device] =
+        create_receiver_program(compile_time_args, receiver_runtime_args, receiver_logical_core);
+    for (const auto& dev : mcast_group_devices) {
+        recv_programs[dev] = create_receiver_program(compile_time_args, receiver_runtime_args, receiver_logical_core);
+    }
+
+    // Launch sender and receiver programs and wait for them to finish
+    for (auto& [dev, recv_program] : recv_programs) {
+        log_info("Run receiver on: {}", dev->id());
+        fixture->RunProgramNonblocking(dev, *recv_program);
+    }
+    log_info("Run Sender on: {}", sender_device->id());
+    fixture->RunProgramNonblocking(sender_device, sender_program);
+
+    for (auto& [dev, recv_program] : recv_programs) {
+        fixture->WaitForSingleProgramDone(dev, *recv_program);
+    }
+    fixture->WaitForSingleProgramDone(sender_device, sender_program);
+
+    std::vector<uint32_t> sender_status;
+    tt_metal::detail::ReadFromDeviceL1(
+        sender_device,
+        sender_logical_core,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
+        sender_status,
+        CoreType::WORKER);
+
+    EXPECT_EQ(sender_status[TT_FABRIC_STATUS_INDEX], TT_FABRIC_STATUS_PASS);
+    uint64_t sender_bytes =
+        ((uint64_t)sender_status[TT_FABRIC_WORD_CNT_INDEX + 1] << 32) | sender_status[TT_FABRIC_WORD_CNT_INDEX];
+
+    for (auto& [dev, _] : recv_programs) {
+        std::vector<uint32_t> receiver_status;
+        tt_metal::detail::ReadFromDeviceL1(
+            dev,
+            receiver_logical_core,
+            worker_mem_map.test_results_address,
+            worker_mem_map.test_results_size_bytes,
+            receiver_status,
+            CoreType::WORKER);
+
+        EXPECT_EQ(receiver_status[TT_FABRIC_STATUS_INDEX], TT_FABRIC_STATUS_PASS);
+        uint64_t receiver_bytes =
+            ((uint64_t)receiver_status[TT_FABRIC_WORD_CNT_INDEX + 1] << 32) | receiver_status[TT_FABRIC_WORD_CNT_INDEX];
+
+        EXPECT_EQ(sender_bytes, receiver_bytes);
+    }
+}
+
 void RunTestUnicastRaw(BaseFabricFixture* fixture, uint32_t num_hops, RoutingDirection direction) {
     CoreCoord sender_logical_core = {0, 0};
     CoreCoord receiver_logical_core = {1, 0};
@@ -240,18 +491,20 @@ void RunTestUnicastRaw(BaseFabricFixture* fixture, uint32_t num_hops, RoutingDir
         tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
 
     // test parameters
-    uint32_t packet_header_address = 0x25000;
-    uint32_t source_l1_buffer_address = 0x30000;
-    uint32_t packet_payload_size_bytes = topology == Topology::Mesh ? 2048 : 4096;
+    auto worker_mem_map = generate_worker_mem_map(sender_device, topology);
     uint32_t num_packets = 10;
-    uint32_t test_results_address = 0x100000;
-    uint32_t test_results_size_bytes = 128;
-    uint32_t target_address = 0x30000;
     uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
 
+    const auto fabric_config = tt::tt_metal::MetalContext::instance().get_cluster().get_fabric_config();
+
     // common compile time args for sender and receiver
     std::vector<uint32_t> compile_time_args = {
-        test_results_address, test_results_size_bytes, target_address, 0 /* mcast_mode */, topology == Topology::Mesh};
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
+        worker_mem_map.target_address,
+        0 /* mcast_mode */,
+        topology == Topology::Mesh,
+        fabric_config == tt_metal::FabricConfig::FABRIC_2D_DYNAMIC};
 
     std::map<string, string> defines = {};
     if (is_2d_fabric) {
@@ -271,15 +524,16 @@ void RunTestUnicastRaw(BaseFabricFixture* fixture, uint32_t num_hops, RoutingDir
             .defines = defines});
 
     std::vector<uint32_t> sender_runtime_args = {
-        packet_header_address,
-        source_l1_buffer_address,
-        packet_payload_size_bytes,
+        worker_mem_map.packet_header_address,
+        worker_mem_map.source_l1_buffer_address,
+        worker_mem_map.packet_payload_size_bytes,
         num_packets,
         receiver_noc_encoding,
         time_seed,
         mesh_shape[1],
         src_mesh_chip_id.second,
         dst_mesh_chip_id.second,
+        dst_mesh_chip_id.first,
         num_hops};
 
     // append the EDM connection rt args
@@ -310,7 +564,7 @@ void RunTestUnicastRaw(BaseFabricFixture* fixture, uint32_t num_hops, RoutingDir
 
     tt_metal::SetRuntimeArgs(sender_program, sender_kernel, sender_logical_core, sender_runtime_args);
 
-    std::vector<uint32_t> receiver_runtime_args = {packet_payload_size_bytes, num_packets, time_seed};
+    std::vector<uint32_t> receiver_runtime_args = {worker_mem_map.packet_payload_size_bytes, num_packets, time_seed};
 
     // Create the receiver program for validation
     auto receiver_program = tt_metal::CreateProgram();
@@ -338,16 +592,16 @@ void RunTestUnicastRaw(BaseFabricFixture* fixture, uint32_t num_hops, RoutingDir
     tt_metal::detail::ReadFromDeviceL1(
         sender_device,
         sender_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         sender_status,
         CoreType::WORKER);
 
     tt_metal::detail::ReadFromDeviceL1(
         receiver_device,
         receiver_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         receiver_status,
         CoreType::WORKER);
 
@@ -393,22 +647,23 @@ void RunTestUnicastConnAPI(BaseFabricFixture* fixture, uint32_t num_hops, Routin
     auto receiver_noc_encoding =
         tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
 
+    const auto fabric_config = tt::tt_metal::MetalContext::instance().get_cluster().get_fabric_config();
     const auto topology = control_plane->get_fabric_context().get_fabric_topology();
     uint32_t is_2d_fabric = topology == Topology::Mesh;
 
     // test parameters
-    uint32_t packet_header_address = 0x25000;
-    uint32_t source_l1_buffer_address = 0x30000;
-    uint32_t packet_payload_size_bytes = topology == Topology::Mesh ? 2048 : 4096;
+    auto worker_mem_map = generate_worker_mem_map(sender_device, topology);
     uint32_t num_packets = 10;
-    uint32_t test_results_address = 0x100000;
-    uint32_t test_results_size_bytes = 128;
-    uint32_t target_address = 0x30000;
     uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
 
     // common compile time args for sender and receiver
     std::vector<uint32_t> compile_time_args = {
-        test_results_address, test_results_size_bytes, target_address, 0 /* mcast_mode */, topology == Topology::Mesh};
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
+        worker_mem_map.target_address,
+        0 /* mcast_mode */,
+        topology == Topology::Mesh,
+        fabric_config == tt_metal::FabricConfig::FABRIC_2D_DYNAMIC};
 
     std::map<string, string> defines = {};
     if (is_2d_fabric) {
@@ -433,15 +688,16 @@ void RunTestUnicastConnAPI(BaseFabricFixture* fixture, uint32_t num_hops, Routin
     tt::log_info(tt::LogTest, "mesh dimension 1 {:x}", mesh_shape[1]);
 
     std::vector<uint32_t> sender_runtime_args = {
-        packet_header_address,
-        source_l1_buffer_address,
-        packet_payload_size_bytes,
+        worker_mem_map.packet_header_address,
+        worker_mem_map.source_l1_buffer_address,
+        worker_mem_map.packet_payload_size_bytes,
         num_packets,
         receiver_noc_encoding,
         time_seed,
         mesh_shape[1],
         src_mesh_chip_id.second,
         dst_mesh_chip_id.second,
+        dst_mesh_chip_id.first,
         num_hops};
 
     // append the EDM connection rt args
@@ -450,7 +706,7 @@ void RunTestUnicastConnAPI(BaseFabricFixture* fixture, uint32_t num_hops, Routin
 
     tt_metal::SetRuntimeArgs(sender_program, sender_kernel, sender_logical_core, sender_runtime_args);
 
-    std::vector<uint32_t> receiver_runtime_args = {packet_payload_size_bytes, num_packets, time_seed};
+    std::vector<uint32_t> receiver_runtime_args = {worker_mem_map.packet_payload_size_bytes, num_packets, time_seed};
 
     // Create the receiver program for validation
     auto receiver_program = tt_metal::CreateProgram();
@@ -478,16 +734,16 @@ void RunTestUnicastConnAPI(BaseFabricFixture* fixture, uint32_t num_hops, Routin
     tt_metal::detail::ReadFromDeviceL1(
         sender_device,
         sender_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         sender_status,
         CoreType::WORKER);
 
     tt_metal::detail::ReadFromDeviceL1(
         receiver_device,
         receiver_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         receiver_status,
         CoreType::WORKER);
 
@@ -546,18 +802,20 @@ void RunTestMCastConnAPI(BaseFabricFixture* fixture) {
     uint32_t is_2d_fabric = topology == Topology::Mesh;
 
     // test parameters
-    uint32_t packet_header_address = 0x25000;
-    uint32_t source_l1_buffer_address = 0x30000;
-    uint32_t packet_payload_size_bytes = topology == Topology::Mesh ? 2048 : 4096;
+    auto worker_mem_map = generate_worker_mem_map(sender_device, topology);
     uint32_t num_packets = 100;
-    uint32_t test_results_address = 0x100000;
-    uint32_t test_results_size_bytes = 128;
-    uint32_t target_address = 0x30000;
     uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
 
+    const auto fabric_config = tt::tt_metal::MetalContext::instance().get_cluster().get_fabric_config();
+
     // common compile time args for sender and receiver
     std::vector<uint32_t> compile_time_args = {
-        test_results_address, test_results_size_bytes, target_address, 1 /* mcast_mode */, topology == Topology::Mesh};
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
+        worker_mem_map.target_address,
+        1 /* mcast_mode */,
+        topology == Topology::Mesh,
+        fabric_config == tt_metal::FabricConfig::FABRIC_2D_DYNAMIC};
 
     std::map<string, string> defines = {};
     if (is_2d_fabric) {
@@ -582,15 +840,16 @@ void RunTestMCastConnAPI(BaseFabricFixture* fixture) {
     tt::log_info(tt::LogTest, "mesh dimension 1 {:x}", mesh_shape[1]);
 
     std::vector<uint32_t> sender_runtime_args = {
-        packet_header_address,
-        source_l1_buffer_address,
-        packet_payload_size_bytes,
+        worker_mem_map.packet_header_address,
+        worker_mem_map.source_l1_buffer_address,
+        worker_mem_map.packet_payload_size_bytes,
         num_packets,
         receiver_noc_encoding,
         time_seed,
         mesh_shape[1],
         src_chip_id,
         left_chip_id,
+        mesh_id.value(),
         1, /* mcast_fwd_hops */
     };
 
@@ -604,7 +863,7 @@ void RunTestMCastConnAPI(BaseFabricFixture* fixture) {
 
     tt_metal::SetRuntimeArgs(sender_program, sender_kernel, sender_logical_core, sender_runtime_args);
 
-    std::vector<uint32_t> receiver_runtime_args = {packet_payload_size_bytes, num_packets, time_seed};
+    std::vector<uint32_t> receiver_runtime_args = {worker_mem_map.packet_payload_size_bytes, num_packets, time_seed};
 
     // Create the receiver program for validation
     auto receiver_program = tt_metal::CreateProgram();
@@ -635,24 +894,24 @@ void RunTestMCastConnAPI(BaseFabricFixture* fixture) {
     tt_metal::detail::ReadFromDeviceL1(
         sender_device,
         sender_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         sender_status,
         CoreType::WORKER);
 
     tt_metal::detail::ReadFromDeviceL1(
         left_recv_device,
         receiver_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         left_recv_status,
         CoreType::WORKER);
 
     tt_metal::detail::ReadFromDeviceL1(
         right_recv_device,
         receiver_logical_core,
-        test_results_address,
-        test_results_size_bytes,
+        worker_mem_map.test_results_address,
+        worker_mem_map.test_results_size_bytes,
         right_recv_status,
         CoreType::WORKER);
 
@@ -675,7 +934,7 @@ TEST_F(Fabric1DFixture, TestUnicastRaw) { RunTestUnicastRaw(this, 1); }
 TEST_F(Fabric1DFixture, TestUnicastConnAPI) { RunTestUnicastConnAPI(this, 1); }
 TEST_F(Fabric1DFixture, TestMCastConnAPI) { RunTestMCastConnAPI(this); }
 
-TEST_F(Fabric1DFixture, TestEDMConnectionStressTestQuick) {
+TEST_F(Fabric1DFixture, DISABLED_TestEDMConnectionStressTestQuick) {
     // Each epoch is a separate program launch with increasing number of workers
     std::vector<size_t> stall_durations_cycles = {0,    100,  200,  300,   400,   700,   1000,  2000,  3000,  4000,
                                                   5000, 7000, 8000, 10000, 20000, 30000, 40000, 50000, 60000, 100000};
diff --git a/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_fabric_apis.cpp b/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_fabric_apis.cpp
index 175d5a36ee01..ab2881932018 100644
--- a/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_fabric_apis.cpp
+++ b/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_fabric_apis.cpp
@@ -740,5 +740,80 @@ TEST_F(Fabric2DPullFixture, TestAsyncRawWriteMulticastMultidirectional) {
     RunAsyncWriteMulticastTest(this, fabric_mode::PULL, true, true);
 }
 
+// 2D Dynamic Routing Unicast Tests
+TEST_F(Fabric2DDynamicFixture, TestUnicastRaw) {
+    for (uint32_t i = 0; i < 10; i++) {
+        RunTestUnicastRaw(this);
+    }
+}
+
+TEST_F(Fabric2DDynamicFixture, TestUnicastConnAPI) { RunTestUnicastConnAPI(this, 1); }
+
+// 2D Dynamic Routing Unidirectional mcast tests (no turns)
+TEST_F(Fabric2DDynamicFixture, TestLineMcastE1Hop) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 1};
+    RunTestLineMcast(this, RoutingDirection::W, {routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestLineMcastE2Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 2};
+    RunTestLineMcast(this, RoutingDirection::W, {routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestLineMcastW1Hop) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 1};
+    RunTestLineMcast(this, RoutingDirection::E, {routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestLineMcastW2Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 2};
+    RunTestLineMcast(this, RoutingDirection::E, {routing_info});
+}
+
+// 2D Dynamic Routing Unidirectional mcast tests (with turns)
+TEST_F(Fabric2DDynamicFixture, TestLineMcastN1HopE3Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 3};
+    RunTestLineMcast(this, RoutingDirection::N, {routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestLineMcastS1HopE3Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 3};
+    RunTestLineMcast(this, RoutingDirection::S, {routing_info});
+}
+TEST_F(Fabric2DDynamicFixture, TestLineMcastN1HopW3Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 3};
+    RunTestLineMcast(this, RoutingDirection::N, {routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestLineMcastS1HopW3Hops) {
+    auto routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 3};
+    RunTestLineMcast(this, RoutingDirection::S, {routing_info});
+}
+
+// 2D Dynamic Routing Bidirectional Mcast Tests, with turns
+TEST_F(Fabric2DDynamicFixture, TestBiDirLineMcastS1HopE1HopW1Hop) {
+    auto e_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 1};
+    auto w_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 1};
+    RunTestLineMcast(this, RoutingDirection::S, {e_routing_info, w_routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestBiDirLineMcastN1HopE1HopW1Hop) {
+    auto e_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 1};
+    auto w_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 1};
+    RunTestLineMcast(this, RoutingDirection::N, {e_routing_info, w_routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestBiDirLineMcastS1HopE2HopsW1Hop) {
+    auto e_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 2};
+    auto w_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 1};
+    RunTestLineMcast(this, RoutingDirection::S, {e_routing_info, w_routing_info});
+}
+
+TEST_F(Fabric2DDynamicFixture, TestBiDirLineMcastS1HopE1HopW2Hops) {
+    auto e_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::E, .num_mcast_hops = 1};
+    auto w_routing_info = McastRoutingInfo{.mcast_dir = RoutingDirection::W, .num_mcast_hops = 2};
+    RunTestLineMcast(this, RoutingDirection::S, {e_routing_info, w_routing_info});
+}
+
 }  // namespace fabric_router_tests
 }  // namespace tt::tt_fabric
diff --git a/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_tx.cpp b/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_tx.cpp
index 70f586265ff6..c10b177c935c 100644
--- a/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_tx.cpp
+++ b/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_tx.cpp
@@ -22,6 +22,7 @@ tt_l1_ptr uint32_t* const test_results = reinterpret_cast<tt_l1_ptr uint32_t*>(t
 constexpr uint32_t target_address = get_compile_time_arg_val(2);
 constexpr bool mcast_mode = get_compile_time_arg_val(3);
 constexpr bool is_2d_fabric = get_compile_time_arg_val(4);
+constexpr bool use_dynamic_routing = get_compile_time_arg_val(5);
 
 inline void setup_connection_and_headers(
     tt::tt_fabric::WorkerToFabricEdmSender& connection,
@@ -63,6 +64,27 @@ inline void send_packet(
     connection.send_payload_blocking_from_address((uint32_t)packet_header, sizeof(PACKET_HEADER_TYPE));
 }
 
+void set_mcast_header(
+    volatile tt_l1_ptr PACKET_HEADER_TYPE* packet_header, const eth_chan_directions& direction, uint32_t num_hops) {
+    uint16_t e_num_hops = 0;
+    uint16_t w_num_hops = 0;
+    uint16_t n_num_hops = 0;
+    uint16_t s_num_hops = 0;
+
+    if (direction == eth_chan_directions::EAST) {
+        e_num_hops = num_hops;
+    } else if (direction == eth_chan_directions::WEST) {
+        w_num_hops = num_hops;
+    } else if (direction == eth_chan_directions::NORTH) {
+        n_num_hops = num_hops;
+    } else if (direction == eth_chan_directions::SOUTH) {
+        s_num_hops = num_hops;
+    }
+
+    fabric_set_mcast_route(
+        (LowLatencyMeshPacketHeader*)packet_header, 0, 0, e_num_hops, w_num_hops, n_num_hops, s_num_hops);
+}
+
 inline void teardown_connection(tt::tt_fabric::WorkerToFabricEdmSender& connection) { connection.close(); }
 
 void kernel_main() {
@@ -78,6 +100,7 @@ void kernel_main() {
     uint32_t ew_dim = get_arg_val<uint32_t>(rt_args_idx++);
     uint32_t my_dev_id = get_arg_val<uint32_t>(rt_args_idx++);
     uint32_t fwd_dev_id = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t fwd_mesh_id = get_arg_val<uint32_t>(rt_args_idx++);
 
     uint64_t noc_dest_addr = get_noc_addr_helper(rx_noc_encoding, target_address);
 
@@ -104,14 +127,8 @@ void kernel_main() {
         zero_l1_buf((uint32_t*)packet_header_buffer_address, sizeof(PACKET_HEADER_TYPE) * 2);
 
         if constexpr (is_2d_fabric) {
-            fabric_set_mcast_route(
-                (LowLatencyMeshPacketHeader*)fwd_packet_header,
-                (eth_chan_directions)fwd_fabric_connection.direction,
-                mcast_fwd_hops);
-            fabric_set_mcast_route(
-                (LowLatencyMeshPacketHeader*)bwd_packet_header,
-                (eth_chan_directions)bwd_fabric_connection.direction,
-                mcast_bwd_hops);
+            set_mcast_header(fwd_packet_header, (eth_chan_directions)fwd_fabric_connection.direction, mcast_fwd_hops);
+            set_mcast_header(bwd_packet_header, (eth_chan_directions)bwd_fabric_connection.direction, mcast_bwd_hops);
         }
 
         setup_connection_and_headers(
@@ -130,12 +147,23 @@ void kernel_main() {
         zero_l1_buf((uint32_t*)packet_header_buffer_address, sizeof(PACKET_HEADER_TYPE));
 
         if constexpr (is_2d_fabric) {
-            fabric_set_unicast_route(
-                (LowLatencyMeshPacketHeader*)packet_header_buffer_address,
-                (eth_chan_directions)fwd_fabric_connection.direction,
-                my_dev_id,
-                fwd_dev_id,
-                ew_dim);
+            if constexpr (use_dynamic_routing) {
+                fabric_set_unicast_route(
+                    (MeshPacketHeader*)packet_header_buffer_address,
+                    (eth_chan_directions)fwd_fabric_connection.direction,
+                    my_dev_id,
+                    fwd_dev_id,
+                    fwd_mesh_id,
+                    ew_dim);
+            } else {
+                fabric_set_unicast_route(
+                    (LowLatencyMeshPacketHeader*)packet_header_buffer_address,
+                    (eth_chan_directions)fwd_fabric_connection.direction,
+                    my_dev_id,
+                    fwd_dev_id,
+                    fwd_mesh_id,
+                    ew_dim);
+            }
         }
 
         setup_connection_and_headers(
diff --git a/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp b/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp
new file mode 100644
index 000000000000..dd052927c740
--- /dev/null
+++ b/tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: © 2025 Tenstorrent Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+// clang-format off
+#include "dataflow_api.h"
+#include "debug/dprint.h"
+#include "tests/tt_metal/tt_metal/perf_microbenchmark/common/kernel_utils.hpp"
+#include "tt_metal/fabric/hw/inc/tt_fabric_status.h"
+#include "tt_metal/fabric/hw/inc/tt_fabric.h"
+#include "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_traffic_gen.hpp"
+#include "tt_metal/api/tt-metalium/fabric_edm_packet_header.hpp"
+#include "tt_metal/fabric/hw/inc/edm_fabric/fabric_connection_manager.hpp"
+#include "tt_metal/fabric/hw/inc/tt_fabric_api.h"
+
+// clang-format on
+
+constexpr uint32_t test_results_addr_arg = get_compile_time_arg_val(0);
+constexpr uint32_t test_results_size_bytes = get_compile_time_arg_val(1);
+tt_l1_ptr uint32_t* const test_results = reinterpret_cast<tt_l1_ptr uint32_t*>(test_results_addr_arg);
+
+constexpr uint32_t target_address = get_compile_time_arg_val(2);
+
+inline void setup_connection_and_headers(
+    tt::tt_fabric::WorkerToFabricEdmSender& connection,
+    volatile tt_l1_ptr PACKET_HEADER_TYPE* packet_header,
+    uint64_t noc_dest_addr,
+    uint32_t packet_payload_size_bytes) {
+    // connect to edm
+    connection.open();
+    packet_header->to_noc_unicast_write(NocUnicastCommandHeader{noc_dest_addr}, packet_payload_size_bytes);
+}
+
+inline void send_packet(
+    volatile tt_l1_ptr PACKET_HEADER_TYPE* packet_header,
+    uint64_t noc_dest_addr,
+    uint32_t source_l1_buffer_address,
+    uint32_t packet_payload_size_bytes,
+    uint32_t seed,
+    tt::tt_fabric::WorkerToFabricEdmSender& connection) {
+    packet_header->to_noc_unicast_write(NocUnicastCommandHeader{noc_dest_addr}, packet_payload_size_bytes);
+    // fill packet data for sanity testing
+    tt_l1_ptr uint32_t* start_addr = reinterpret_cast<tt_l1_ptr uint32_t*>(source_l1_buffer_address);
+    fill_packet_data(start_addr, packet_payload_size_bytes / 16, seed);
+    tt_l1_ptr uint32_t* last_word_addr =
+        reinterpret_cast<tt_l1_ptr uint32_t*>(source_l1_buffer_address + packet_payload_size_bytes - 4);
+    connection.wait_for_empty_write_slot();
+    connection.send_payload_without_header_non_blocking_from_address(
+        source_l1_buffer_address, packet_payload_size_bytes);
+    connection.send_payload_blocking_from_address((uint32_t)packet_header, sizeof(PACKET_HEADER_TYPE));
+}
+
+inline void teardown_connection(tt::tt_fabric::WorkerToFabricEdmSender& connection) { connection.close(); }
+
+void kernel_main() {
+    using namespace tt::tt_fabric;
+
+    size_t rt_args_idx = 0;
+    uint32_t packet_header_buffer_address = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t source_l1_buffer_address = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t packet_payload_size_bytes = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t num_packets = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t rx_noc_encoding = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t time_seed = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t fwd_dev_id = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t fwd_mesh_id = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t num_hops_e = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t num_hops_w = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t num_hops_n = get_arg_val<uint32_t>(rt_args_idx++);
+    uint32_t num_hops_s = get_arg_val<uint32_t>(rt_args_idx++);
+    uint64_t noc_dest_addr = get_noc_addr_helper(rx_noc_encoding, target_address);
+    tt::tt_fabric::WorkerToFabricEdmSender fwd_fabric_connection;
+
+    volatile tt_l1_ptr PACKET_HEADER_TYPE* fwd_packet_header;
+    volatile tt_l1_ptr PACKET_HEADER_TYPE* bwd_packet_header;
+
+    fwd_fabric_connection =
+        tt::tt_fabric::WorkerToFabricEdmSender::build_from_args<ProgrammableCoreType::TENSIX>(rt_args_idx);
+
+    fwd_packet_header = reinterpret_cast<volatile tt_l1_ptr PACKET_HEADER_TYPE*>(packet_header_buffer_address);
+
+    zero_l1_buf((uint32_t*)packet_header_buffer_address, sizeof(PACKET_HEADER_TYPE));
+
+    fabric_set_mcast_route(
+        (MeshPacketHeader*)packet_header_buffer_address,
+        fwd_dev_id,
+        fwd_mesh_id,
+        num_hops_e,
+        num_hops_w,
+        num_hops_n,
+        num_hops_s);
+
+    setup_connection_and_headers(fwd_fabric_connection, fwd_packet_header, noc_dest_addr, packet_payload_size_bytes);
+
+    zero_l1_buf(test_results, test_results_size_bytes);
+    test_results[TT_FABRIC_STATUS_INDEX] = TT_FABRIC_STATUS_STARTED;
+
+    uint64_t start_timestamp = get_timestamp();
+
+    // loop over for num packets
+    for (uint32_t i = 0; i < num_packets; i++) {
+        time_seed = prng_next(time_seed);
+        DPRINT << "Send packet" << ENDL();
+        send_packet(
+            fwd_packet_header,
+            noc_dest_addr,
+            source_l1_buffer_address,
+            packet_payload_size_bytes,
+            time_seed,
+            fwd_fabric_connection);
+
+        noc_dest_addr += packet_payload_size_bytes;
+    }
+
+    uint64_t cycles_elapsed = get_timestamp() - start_timestamp;
+
+    teardown_connection(fwd_fabric_connection);
+    noc_async_write_barrier();
+
+    uint64_t bytes_sent = packet_payload_size_bytes * num_packets;
+
+    // write out results
+    test_results[TT_FABRIC_STATUS_INDEX] = TT_FABRIC_STATUS_PASS;
+    test_results[TT_FABRIC_CYCLES_INDEX] = (uint32_t)cycles_elapsed;
+    test_results[TT_FABRIC_CYCLES_INDEX + 1] = cycles_elapsed >> 32;
+    test_results[TT_FABRIC_WORD_CNT_INDEX] = (uint32_t)bytes_sent;
+    test_results[TT_FABRIC_WORD_CNT_INDEX + 1] = bytes_sent >> 32;
+}
diff --git a/tt_metal/api/tt-metalium/fabric_edm_packet_header.hpp b/tt_metal/api/tt-metalium/fabric_edm_packet_header.hpp
index 3be84aa6a08e..0d28f6b1f5fc 100644
--- a/tt_metal/api/tt-metalium/fabric_edm_packet_header.hpp
+++ b/tt_metal/api/tt-metalium/fabric_edm_packet_header.hpp
@@ -537,6 +537,18 @@ struct LowLatencyMeshPacketHeader : public PacketHeaderBase<LowLatencyMeshPacket
     void to_chip_multicast_impl(const MulticastRoutingCommandHeader& chip_multicast_command_header) volatile {}
 };
 
+struct MeshPacketHeader : public PacketHeaderBase<MeshPacketHeader> {
+    uint16_t dst_start_chip_id;
+    uint16_t dst_start_mesh_id;
+    uint16_t mcast_params[4];
+    uint8_t is_mcast_active;
+    void to_chip_unicast_impl(uint8_t distance_in_hops) {}
+    void to_chip_multicast_impl(const MulticastRoutingCommandHeader& chip_multicast_command_header) {}
+
+    void to_chip_unicast_impl(uint8_t distance_in_hops) volatile {}
+    void to_chip_multicast_impl(const MulticastRoutingCommandHeader& chip_multicast_command_header) volatile {}
+};
+
 // TODO: When we remove the 32B padding requirement, reduce to 16B size check
 static_assert(sizeof(PacketHeader) == 32, "sizeof(PacketHeader) is not equal to 32B");
 // Host code still hardcoded to sizeof(PacketHeader) so we need to keep this check
@@ -551,15 +563,18 @@ static_assert(sizeof(LowLatencyMeshPacketHeader) == 64, "sizeof(LowLatencyPacket
 #define PACKET_HEADER_TYPE tt::tt_fabric::LowLatencyPacketHeader
 #define ROUTING_FIELDS_TYPE tt::tt_fabric::LowLatencyRoutingFields
 #else
-#if ((ROUTING_MODE & ROUTING_MODE_DYNAMIC)) == ROUTING_MODE_DYNAMIC
-static_assert(false, "ROUTING_MODE_DYNAMIC is not supported yet");
-#elif (                                                              \
+
+#if (                                                                \
     ((ROUTING_MODE & (ROUTING_MODE_1D | ROUTING_MODE_LINE)) != 0) || \
     ((ROUTING_MODE & (ROUTING_MODE_1D | ROUTING_MODE_RING)) != 0))
-#if ((ROUTING_MODE & ROUTING_MODE_LOW_LATENCY)) != 0
+// Dynamic Routing with 1D Fabric is not supported
+#if ((ROUTING_MODE & ROUTING_MODE_DYNAMIC)) == ROUTING_MODE_DYNAMIC
+static_assert(false, "ROUTING_MODE_DYNAMIC is not supported yet");
 
+#elif ((ROUTING_MODE & ROUTING_MODE_LOW_LATENCY)) != 0
 #define PACKET_HEADER_TYPE tt::tt_fabric::LowLatencyPacketHeader
 #define ROUTING_FIELDS_TYPE tt::tt_fabric::LowLatencyRoutingFields
+
 #else
 #define PACKET_HEADER_TYPE tt::tt_fabric::PacketHeader
 #define ROUTING_FIELDS_TYPE tt::tt_fabric::RoutingFields
@@ -574,6 +589,10 @@ static_assert(false, "ROUTING_MODE_DYNAMIC is not supported yet");
 #if (ROUTING_MODE & ROUTING_MODE_LOW_LATENCY) != 0
 #define PACKET_HEADER_TYPE tt::tt_fabric::LowLatencyMeshPacketHeader
 #define ROUTING_FIELDS_TYPE tt::tt_fabric::LowLatencyMeshRoutingFields
+#elif ((ROUTING_MODE & ROUTING_MODE_DYNAMIC)) == ROUTING_MODE_DYNAMIC
+#define DYNAMIC_ROUTING_ENABLED 1
+#define PACKET_HEADER_TYPE tt::tt_fabric::MeshPacketHeader
+#define ROUTING_FIELDS_TYPE tt::tt_fabric::LowLatencyMeshRoutingFields
 #else
 #define PACKET_HEADER_TYPE packet_header_t
 #endif
diff --git a/tt_metal/api/tt-metalium/fabric_types.hpp b/tt_metal/api/tt-metalium/fabric_types.hpp
index 4a7f8fe902df..bbd0365eae24 100644
--- a/tt_metal/api/tt-metalium/fabric_types.hpp
+++ b/tt_metal/api/tt-metalium/fabric_types.hpp
@@ -7,11 +7,12 @@
 namespace tt::tt_metal {
 enum class FabricConfig : uint32_t {
     DISABLED = 0,
-    FABRIC_1D = 1,       // Instatiates fabric with 1D routing and no deadlock avoidance
-    FABRIC_1D_RING = 2,  // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
-    FABRIC_2D = 3,       // Instatiates fabric with 2D routing in pull mode
-    FABRIC_2D_PUSH = 4,  // Instatiates fabric with 2D routing in push mode
-    CUSTOM = 5
+    FABRIC_1D = 1,          // Instatiates fabric with 1D routing and no deadlock avoidance
+    FABRIC_1D_RING = 2,     // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
+    FABRIC_2D = 3,          // Instatiates fabric with 2D routing in pull mode
+    FABRIC_2D_PUSH = 4,     // Instatiates fabric with 2D routing in push mode
+    FABRIC_2D_DYNAMIC = 5,  // Instatiates fabric with 2D routing in push mode with dynamic routing
+    CUSTOM = 6
 };
 
 }  // namespace tt::tt_metal
diff --git a/tt_metal/fabric/erisc_datamover_builder.cpp b/tt_metal/fabric/erisc_datamover_builder.cpp
index 22360cece491..68354a7be889 100644
--- a/tt_metal/fabric/erisc_datamover_builder.cpp
+++ b/tt_metal/fabric/erisc_datamover_builder.cpp
@@ -22,6 +22,7 @@
 #include <variant>
 #include <vector>
 
+#include "impl/context/metal_context.hpp"
 #include "tt_metal/fabric/fabric_host_utils.hpp"
 #include "core_coord.hpp"
 #include "fabric_edm_types.hpp"
@@ -137,7 +138,6 @@ FabricEriscDatamoverConfig::FabricEriscDatamoverConfig(std::size_t channel_buffe
         this->num_used_receiver_channels -= 1;
         this->num_fwd_paths -= 1;
     }
-    tt::tt_fabric::set_routing_mode(topology);
 
     for (uint32_t i = 0; i < this->num_used_receiver_channels; i++) {
         TT_FATAL(
@@ -434,6 +434,8 @@ std::vector<uint32_t> FabricEriscDatamoverBuilder::get_compile_time_args() const
 
     size_t num_sender_channels = config.num_used_sender_channels;
     size_t num_receiver_channels = config.num_used_receiver_channels;
+    auto& soc_desc = tt::tt_metal::MetalContext::instance().get_cluster().get_soc_desc(this->my_chip_id);
+
     auto ct_args = std::vector<uint32_t>{
         num_sender_channels,
         num_receiver_channels,
@@ -507,6 +509,7 @@ std::vector<uint32_t> FabricEriscDatamoverBuilder::get_compile_time_args() const
         FabricEriscDatamoverConfig::sender_completed_packet_header_cb_size_headers,
         config.topology == Topology::Mesh,
         this->direction,
+        soc_desc.get_num_eth_channels(),
         // Special marker to help with identifying misalignment bugs
         0x00c0ffee};
 
diff --git a/tt_metal/fabric/fabric_context.cpp b/tt_metal/fabric/fabric_context.cpp
index 21ef2f6be491..b64c9707fb46 100644
--- a/tt_metal/fabric/fabric_context.cpp
+++ b/tt_metal/fabric/fabric_context.cpp
@@ -44,6 +44,7 @@ tt::tt_fabric::Topology FabricContext::get_topology() const {
         case tt::tt_metal::FabricConfig::FABRIC_1D_RING: return tt::tt_fabric::Topology::Ring;
         case tt::tt_metal::FabricConfig::FABRIC_2D_PUSH: return tt::tt_fabric::Topology::Mesh;
         case tt::tt_metal::FabricConfig::FABRIC_2D: return tt::tt_fabric::Topology::Mesh;
+        case tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC: return tt::tt_fabric::Topology::Mesh;
         case tt::tt_metal::FabricConfig::DISABLED:
         case tt::tt_metal::FabricConfig::CUSTOM:
             TT_THROW("Unsupported fabric config: {}", magic_enum::enum_name(this->fabric_config_));
@@ -84,6 +85,7 @@ FabricContext::FabricContext(tt::tt_metal::FabricConfig fabric_config) {
     if (is_tt_fabric_config(this->fabric_config_)) {
         this->router_config_ = std::make_unique<tt::tt_fabric::FabricEriscDatamoverConfig>(
             this->channel_buffer_size_bytes_, this->topology_);
+        set_routing_mode(this->topology_, this->fabric_config_);
     } else {
         this->router_config_ = nullptr;
     }
diff --git a/tt_metal/fabric/fabric_host_utils.cpp b/tt_metal/fabric/fabric_host_utils.cpp
index c451bce08a81..140839c44d5a 100644
--- a/tt_metal/fabric/fabric_host_utils.cpp
+++ b/tt_metal/fabric/fabric_host_utils.cpp
@@ -24,12 +24,14 @@ namespace tt::tt_fabric {
 bool is_tt_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D ||
            fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D_RING ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
 }
 
 bool is_2d_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
 }
 
 uint32_t get_sender_channel_count(tt::tt_fabric::Topology topology) {
@@ -115,7 +117,7 @@ void set_routing_mode(uint16_t routing_mode) {
     control_plane->set_routing_mode(routing_mode);
 }
 
-void set_routing_mode(Topology topology, uint32_t dimension /*, take more*/) {
+void set_routing_mode(Topology topology, tt::tt_metal::FabricConfig fabric_config, uint32_t dimension /*, take more*/) {
     // TODO: take more parameters to set detail routing mode
     TT_FATAL(
         dimension == 1 || dimension == 2 || dimension == 3,
@@ -130,8 +132,11 @@ void set_routing_mode(Topology topology, uint32_t dimension /*, take more*/) {
     } else if (topology == Topology::Mesh) {
         mode |= (ROUTING_MODE_2D | ROUTING_MODE_MESH);
     }
-
-    mode |= ROUTING_MODE_LOW_LATENCY;
+    if (fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC) {
+        mode |= ROUTING_MODE_DYNAMIC;
+    } else {
+        mode |= ROUTING_MODE_LOW_LATENCY;
+    }
     set_routing_mode(mode);
 }
 
diff --git a/tt_metal/fabric/fabric_host_utils.hpp b/tt_metal/fabric/fabric_host_utils.hpp
index 755e4ce86342..da1610db1726 100644
--- a/tt_metal/fabric/fabric_host_utils.hpp
+++ b/tt_metal/fabric/fabric_host_utils.hpp
@@ -23,7 +23,7 @@ uint32_t get_sender_channel_count(tt::tt_fabric::Topology topology);
 uint32_t get_downstream_edm_count(tt::tt_fabric::Topology topology);
 
 void set_routing_mode(uint16_t routing_mode);
-void set_routing_mode(Topology topology, uint32_t dimension = 1);
+void set_routing_mode(Topology topology, tt::tt_metal::FabricConfig fabric_config, uint32_t dimension = 1);
 
 FabricType get_fabric_type(tt::tt_metal::FabricConfig fabric_config, tt::ClusterType cluster_type);
 
diff --git a/tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp b/tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
index e30293c81a9a..ab8fa93a7446 100644
--- a/tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
+++ b/tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
@@ -173,7 +173,9 @@ constexpr size_t sender_4_completed_packet_header_cb_size_headers =
 constexpr size_t is_2d_fabric = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 56);
 constexpr size_t my_direction = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 57);
 
-constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 58;
+constexpr size_t num_eth_ports = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 58);
+
+constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 59;
 constexpr size_t SPECIAL_MARKER_0 = 0x00c0ffee;
 static_assert(
     !SPECIAL_MARKER_CHECK_ENABLED || get_compile_time_arg_val(SPECIAL_MARKER_0_IDX) == SPECIAL_MARKER_0,
diff --git a/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_header_validate.hpp b/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_header_validate.hpp
index 24632d6a690b..ab4f4e8282e6 100644
--- a/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_header_validate.hpp
+++ b/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_header_validate.hpp
@@ -26,4 +26,12 @@ FORCE_INLINE bool is_valid(const LowLatencyMeshPacketHeader& packet_header) {
     return (packet_header.noc_send_type <= NOC_SEND_TYPE_LAST);
 }
 
+FORCE_INLINE void validate(const MeshPacketHeader& packet_header) {
+    ASSERT(packet_header.noc_send_type <= NOC_SEND_TYPE_LAST);
+}
+
+FORCE_INLINE bool is_valid(const MeshPacketHeader& packet_header) {
+    return (packet_header.noc_send_type <= NOC_SEND_TYPE_LAST);
+}
+
 }  // namespace tt::tt_fabric
diff --git a/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_transmission.hpp b/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_transmission.hpp
index 88d01d5d6fb6..03d305a39326 100644
--- a/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_transmission.hpp
+++ b/tt_metal/fabric/hw/inc/edm_fabric/fabric_edm_packet_transmission.hpp
@@ -240,6 +240,10 @@ FORCE_INLINE void update_packet_header_for_next_hop(
     packet_header->routing_fields.value = cached_routing_fields.value + 1;
 }
 
+FORCE_INLINE void update_packet_header_for_next_hop(
+    volatile tt_l1_ptr tt::tt_fabric::MeshPacketHeader* packet_header,
+    tt::tt_fabric::LowLatencyMeshRoutingFields cached_routing_fields) {}
+
 // This function forwards a packet to the downstream EDM channel for eventual sending
 // to the next chip in the line/ring
 //
diff --git a/tt_metal/fabric/hw/inc/tt_fabric_api.h b/tt_metal/fabric/hw/inc/tt_fabric_api.h
index 9f1613e9ee44..43059435dd2f 100644
--- a/tt_metal/fabric/hw/inc/tt_fabric_api.h
+++ b/tt_metal/fabric/hw/inc/tt_fabric_api.h
@@ -411,11 +411,46 @@ void fabric_set_route(
     packet_header->routing_fields.value = 0;
 }
 
+void fabric_set_unicast_route(
+    MeshPacketHeader* packet_header,
+    eth_chan_directions outgoing_direction,  // Ignore this: Dynamic Routing does not need outgoing_direction specified
+    uint16_t my_dev_id,                      // Ignore this: Dynamic Routing does not need src chip ID
+    uint16_t dst_dev_id,
+    uint16_t dst_mesh_id,
+    uint16_t ew_dim  // Ignore this: Dynamic Routing does not need mesh dimensions
+) {
+    packet_header->dst_start_chip_id = dst_dev_id;
+    packet_header->dst_start_mesh_id = dst_mesh_id;
+    packet_header->mcast_params[0] = 0;
+    packet_header->mcast_params[1] = 0;
+    packet_header->mcast_params[2] = 0;
+    packet_header->mcast_params[3] = 0;
+    packet_header->is_mcast_active = 0;
+}
+
+void fabric_set_mcast_route(
+    MeshPacketHeader* packet_header,
+    uint16_t dst_dev_id,
+    uint16_t dst_mesh_id,
+    uint16_t e_num_hops,
+    uint16_t w_num_hops,
+    uint16_t n_num_hops,
+    uint16_t s_num_hops) {
+    packet_header->dst_start_chip_id = dst_dev_id;
+    packet_header->dst_start_mesh_id = dst_mesh_id;
+    packet_header->mcast_params[0] = e_num_hops;
+    packet_header->mcast_params[1] = w_num_hops;
+    packet_header->mcast_params[2] = n_num_hops;
+    packet_header->mcast_params[3] = s_num_hops;
+    packet_header->is_mcast_active = 0;
+}
+
 void fabric_set_unicast_route(
     LowLatencyMeshPacketHeader* packet_header,
     eth_chan_directions outgoing_direction,
     uint16_t my_dev_id,
     uint16_t dst_dev_id,
+    uint16_t dst_mesh_id,  // Ignore this, since Low Latency Mesh Fabric is not used for Inter-Mesh Routing
     uint16_t ew_dim) {
     if (outgoing_direction == eth_chan_directions::EAST || outgoing_direction == eth_chan_directions::WEST) {
         uint32_t ew_hops = my_dev_id < dst_dev_id ? dst_dev_id - my_dev_id : my_dev_id - dst_dev_id;
@@ -451,8 +486,23 @@ void fabric_set_unicast_route(
     }
 }
 
-void fabric_set_mcast_route(LowLatencyMeshPacketHeader* packet_header, eth_chan_directions direction, uint32_t hops) {
-    fabric_set_route<true>(packet_header, (eth_chan_directions)direction, 0, hops);
+void fabric_set_mcast_route(
+    LowLatencyMeshPacketHeader* packet_header,
+    uint16_t dst_dev_id,   // Ignore this, since Low Latency Mesh Fabric does not support arbitrary 2D Mcasts yet
+    uint16_t dst_mesh_id,  // Ignore this, since Low Latency Mesh Fabric is not used for Inter-Mesh Routing
+    uint16_t e_num_hops,
+    uint16_t w_num_hops,
+    uint16_t n_num_hops,
+    uint16_t s_num_hops) {
+    if (e_num_hops) {
+        fabric_set_route<true>(packet_header, eth_chan_directions::EAST, 0, e_num_hops);
+    } else if (w_num_hops) {
+        fabric_set_route<true>(packet_header, eth_chan_directions::WEST, 0, w_num_hops);
+    } else if (n_num_hops) {
+        fabric_set_route<true>(packet_header, eth_chan_directions::NORTH, 0, n_num_hops);
+    } else if (s_num_hops) {
+        fabric_set_route<true>(packet_header, eth_chan_directions::NORTH, 0, s_num_hops);
+    }
 }
 
 inline void fabric_client_connect(
diff --git a/tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp b/tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
index 1e55ad8c6f00..783556d2533c 100644
--- a/tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
+++ b/tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
@@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE bool can_forward_packet_completely(
+    tt_l1_ptr MeshPacketHeader* packet_header,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    if (packet_header->is_mcast_active) {
+        // mcast downstream needs to check if downstream has space (lookup from set direction field)
+        // forward to local and remote
+        bool has_space = true;
+        // If the current chip is part of an mcast group, stall until all downstream mcast receivers have
+        // space
+        for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+            if (packet_header->mcast_params[i] and i != my_direction) {
+                has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+            }
+        }
+        return has_space;
+    } else {
+        // check if header matches curr. If so, check mcast fields, set mcast true and forward to specific direction
+        auto dest_chip_id = packet_header->dst_start_chip_id;
+        auto dest_mesh_id = packet_header->dst_start_mesh_id;
+        tt_l1_ptr fabric_router_l1_config_t* routing_table =
+            reinterpret_cast<tt_l1_ptr fabric_router_l1_config_t*>(eth_l1_mem::address_map::FABRIC_ROUTER_CONFIG_BASE);
+
+        if (dest_mesh_id != routing_table->my_mesh_id) {
+            uint32_t downstream_channel = routing_table->inter_mesh_table.dest_entry[dest_mesh_id];
+            auto downstream_direction = port_direction_table[downstream_channel];
+            return downstream_edm_interface[downstream_direction].edm_has_space_for_packet();
+        } else {
+            if (dest_chip_id == routing_table->my_device_id) {
+                // Packet has reached its intended chip. Check if this is an mcast or unicast txn.
+                // If mcast, this packet needs to be forwarded to remote and unicasted locally.
+                bool mcast_active = false;
+                bool has_space = true;
+                for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+                    if (packet_header->mcast_params[i]) {
+                        mcast_active = true;
+                        has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+                    }
+                }
+                // Set mcast mode if a valid mcast directions are specified
+                packet_header->is_mcast_active = mcast_active;
+                return has_space;
+            } else {
+                // Unicast packet needs to be forwarded
+                auto downstream_channel = routing_table->intra_mesh_table.dest_entry[(uint8_t)dest_chip_id];
+                auto downstream_direction = port_direction_table[downstream_channel];
+                return downstream_edm_interface[downstream_direction].edm_has_space_for_packet();
+            }
+        }
+    }
+}
+
 template <uint8_t SENDER_NUM_BUFFERS>
 FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
     uint32_t hop_cmd,
@@ -548,6 +601,65 @@ FORCE_INLINE void receiver_forward_packet(
     }
 }
 
+#if defined(FABRIC_2D) && defined(DYNAMIC_ROUTING_ENABLED)
+// !!!WARNING!!! - MAKE SURE CONSUMER HAS SPACE BEFORE CALLING
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) void receiver_forward_packet(
+    tt_l1_ptr PACKET_HEADER_TYPE* packet_start,
+    ROUTING_FIELDS_TYPE cached_routing_fields,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    uint8_t transaction_id,
+    uint8_t rx_channel_id,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    auto dest_mesh_id = packet_start->dst_start_mesh_id;
+    auto dest_chip_id = packet_start->dst_start_chip_id;
+    auto mcast_active = packet_start->is_mcast_active;
+
+    uint16_t payload_size_bytes = packet_start->payload_size_bytes;
+    tt_l1_ptr fabric_router_l1_config_t* routing_table =
+        reinterpret_cast<tt_l1_ptr fabric_router_l1_config_t*>(eth_l1_mem::address_map::FABRIC_ROUTER_CONFIG_BASE);
+
+    if (dest_mesh_id != routing_table->my_mesh_id) {
+        uint32_t downstream_channel = routing_table->inter_mesh_table.dest_entry[dest_mesh_id];
+        auto downstream_direction = port_direction_table[downstream_channel];
+        forward_payload_to_downstream_edm<SENDER_NUM_BUFFERS, enable_ring_support, false>(
+            packet_start,
+            payload_size_bytes,
+            cached_routing_fields,
+            downstream_edm_interface[downstream_direction],
+            transaction_id);
+    } else {
+        if (dest_chip_id == routing_table->my_device_id || mcast_active) {
+            execute_chip_unicast_to_local_chip(packet_start, payload_size_bytes, transaction_id, rx_channel_id);
+            if (mcast_active) {
+                // This packet is in an active mcast
+                for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+                    if (packet_start->mcast_params[i] and i != my_direction) {
+                        packet_start->mcast_params[i]--;
+                        forward_payload_to_downstream_edm<SENDER_NUM_BUFFERS, enable_ring_support, false>(
+                            packet_start,
+                            payload_size_bytes,
+                            cached_routing_fields,
+                            downstream_edm_interface[i],
+                            transaction_id);
+                    }
+                }
+            }
+        } else {
+            // Unicast forward packet to downstream
+            auto downstream_channel = routing_table->intra_mesh_table.dest_entry[dest_chip_id];
+            auto downstream_direction = port_direction_table[downstream_channel];
+            forward_payload_to_downstream_edm<SENDER_NUM_BUFFERS, enable_ring_support, false>(
+                packet_start,
+                payload_size_bytes,
+                cached_routing_fields,
+                downstream_edm_interface[downstream_direction],
+                transaction_id);
+        }
+    }
+}
+#endif
+
 // !!!WARNING!!! - MAKE SURE CONSUMER HAS SPACE BEFORE CALLING
 template <uint8_t SENDER_NUM_BUFFERS>
 FORCE_INLINE __attribute__((optimize("jump-tables"))) void receiver_forward_packet(
@@ -769,7 +881,8 @@ void run_receiver_channel_step(
     ReceiverChannelPointers<RECEIVER_NUM_BUFFERS>& receiver_channel_pointers,
     PacketHeaderRecorder& packet_header_recorder,
     WriteTridTracker& receiver_channel_trid_tracker,
-    uint8_t rx_channel_id) {
+    uint8_t rx_channel_id,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
     auto& ack_counter = receiver_channel_pointers.ack_counter;
     auto pkts_received_since_last_check = get_ptr_val<to_receiver_pkts_sent_id>();
     if constexpr (enable_first_level_ack) {
@@ -793,7 +906,11 @@ void run_receiver_channel_step(
         tt_l1_ptr PACKET_HEADER_TYPE* packet_header = const_cast<PACKET_HEADER_TYPE*>(
             local_receiver_channel.template get_packet_header<PACKET_HEADER_TYPE>(receiver_buffer_index));
 
-        ROUTING_FIELDS_TYPE cached_routing_fields = packet_header->routing_fields;
+        ROUTING_FIELDS_TYPE cached_routing_fields;
+#if !defined(FABRIC_2D) || !defined(DYNAMIC_ROUTING_ENABLED)
+        cached_routing_fields = packet_header->routing_fields;
+#endif
+
         receiver_channel_pointers.set_src_chan_id(receiver_buffer_index, packet_header->src_ch_id);
         uint32_t hop_cmd;
         bool can_send_to_all_local_chip_receivers;
@@ -812,11 +929,15 @@ void run_receiver_channel_step(
             //  - Hop command of [0010] instructs fabric router to write the packet locally.
             //  - Hop command of [0011] instructs fabric router to write the packet locally AND forward East (a line
             //  mcast)
-#ifdef FABRIC_2D
+#if defined(FABRIC_2D) && defined(DYNAMIC_ROUTING_ENABLED)
+            // need this ifdef since the 2D dynamic routing packet header contains unique fields
+            can_send_to_all_local_chip_receivers =
+                can_forward_packet_completely(packet_header, downstream_edm_interface, port_direction_table);
+#elif defined(FABRIC_2D)
             // need this ifdef since the packet header for 1D does not have router_buffer field in it.
             hop_cmd = packet_header->route_buffer[cached_routing_fields.value];
-#endif
             can_send_to_all_local_chip_receivers = can_forward_packet_completely(hop_cmd, downstream_edm_interface);
+#endif
         } else {
             can_send_to_all_local_chip_receivers =
                 can_forward_packet_completely(cached_routing_fields, downstream_edm_interface[receiver_channel]);
@@ -827,8 +948,18 @@ void run_receiver_channel_step(
             uint8_t trid = receiver_channel_trid_tracker.update_buffer_slot_to_next_trid_and_advance_trid_counter(
                 receiver_buffer_index);
             if constexpr (is_2d_fabric) {
+#if defined(DYNAMIC_ROUTING_ENABLED)
+                receiver_forward_packet(
+                    packet_header,
+                    cached_routing_fields,
+                    downstream_edm_interface,
+                    trid,
+                    rx_channel_id,
+                    port_direction_table);
+#else
                 receiver_forward_packet(
                     packet_header, cached_routing_fields, downstream_edm_interface, trid, rx_channel_id, hop_cmd);
+#endif
             } else {
                 receiver_forward_packet(
                     packet_header,
@@ -955,7 +1086,8 @@ void run_fabric_edm_main_loop(
     std::array<PacketHeaderRecorder, MAX_NUM_SENDER_CHANNELS>& sender_channel_packet_recorders,
     WriteTransactionIdTracker<RECEIVER_NUM_BUFFERS, NUM_TRANSACTION_IDS, 0>& receiver_channel_0_trid_tracker,
     WriteTransactionIdTracker<RECEIVER_NUM_BUFFERS, NUM_TRANSACTION_IDS, NUM_TRANSACTION_IDS>&
-        receiver_channel_1_trid_tracker) {
+        receiver_channel_1_trid_tracker,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
     size_t did_nothing_count = 0;
     *termination_signal_ptr = tt::tt_fabric::TerminationSignal::KEEP_RUNNING;
 
@@ -1029,7 +1161,8 @@ void run_fabric_edm_main_loop(
                     receiver_channel_pointers[0],
                     receiver_channel_packet_recorders[0],
                     receiver_channel_0_trid_tracker,
-                    0);
+                    0,
+                    port_direction_table);
             }
             if constexpr (enable_ring_support) {
                 run_receiver_channel_step<
@@ -1047,7 +1180,8 @@ void run_fabric_edm_main_loop(
                     receiver_channel_pointers[1],
                     receiver_channel_packet_recorders[1],
                     receiver_channel_1_trid_tracker,
-                    1);
+                    1,
+                    port_direction_table);
             }
 
             run_sender_channel_step<
@@ -1763,6 +1897,19 @@ void kernel_main() {
             }
         }
     }
+    std::array<uint8_t, num_eth_ports> port_direction_table;
+#if defined(FABRIC_2D) && defined(DYNAMIC_ROUTING_ENABLED)
+    tt_l1_ptr fabric_router_l1_config_t* routing_table =
+        reinterpret_cast<tt_l1_ptr fabric_router_l1_config_t*>(eth_l1_mem::address_map::FABRIC_ROUTER_CONFIG_BASE);
+
+    for (uint32_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+        auto forwarding_channel = routing_table->port_direction.directions[i];
+        if (forwarding_channel <= num_eth_ports - 1) {
+            // A valid port/eth channel was found for this direction. Specify the port to direction lookup
+            port_direction_table[forwarding_channel] = i;
+        }
+    }
+#endif
 
     wait_for_static_connection_to_ready(local_sender_channel_worker_interfaces);
 
@@ -1788,11 +1935,16 @@ void kernel_main() {
         remote_receiver_channels,
         termination_signal_ptr,
         {receiver_0_channel_counters_ptr, receiver_1_channel_counters_ptr},
-        {sender_channel_0_counters_ptr, sender_channel_1_counters_ptr, sender_channel_2_counters_ptr, sender_channel_3_counters_ptr, sender_channel_4_counters_ptr},
+        {sender_channel_0_counters_ptr,
+         sender_channel_1_counters_ptr,
+         sender_channel_2_counters_ptr,
+         sender_channel_3_counters_ptr,
+         sender_channel_4_counters_ptr},
         receiver_channel_packet_recorders,
         sender_channel_packet_recorders,
         receiver_channel_0_trid_tracker,
-        receiver_channel_1_trid_tracker);
+        receiver_channel_1_trid_tracker,
+        port_direction_table);
 
     if constexpr (persistent_mode) {
         // we force these values to a non-zero value so that if we run the fabric back to back,


Reviews:
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:38:29Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:43:35Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:47:40Z
user: login: ubcheema
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:58:27Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T18:00:24Z
user: login: ubcheema
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:08:42Z
user: login: ubcheema
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:11:46Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:16:17Z
user: login: afuller-TT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:23:21Z
user: login: afuller-TT
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:23:56Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T19:32:05Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:07:11Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:11:28Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:11:53Z
user: login: abhullar-tt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:13:11Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:14:04Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:16:54Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T20:49:34Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T21:23:30Z
user: login: daminakaTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T21:58:13Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T22:34:38Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:16Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:31Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:36Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:41Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:45Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:01:50Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T23:02:52Z
user: login: aagarwalTT
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T00:01:48Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T00:02:51Z
user: login: tt-asaigal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T00:06:07Z

Review comments:
diff_hunk: @@ -129,6 +139,211 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto edm_config = get_tt_fabric_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
+    uint32_t source_l1_buffer_address = 0x30000;
+    uint32_t packet_payload_size_bytes = edm_config.topology == Topology::Mesh ? 2048 : 4096;
+    uint32_t num_packets = 100;
+    uint32_t test_results_address = 0x100000;
+    uint32_t test_results_size_bytes = 128;
+    uint32_t target_address = 0x30000;
+    uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
+
+    // common compile time args for sender and receiver
+    std::vector<uint32_t> compile_time_args = {test_results_address, test_results_size_bytes, target_address};
+
+    std::map<string, string> defines = {};
+    if (is_2d_fabric) {
+        defines["FABRIC_2D"] = "";
+    }
+
+    auto sender_program = tt_metal::CreateProgram();
+    auto sender_kernel = tt_metal::CreateKernel(
+        sender_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp",
+        {sender_logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args,
+            .defines = defines});
+
+    std::vector<uint32_t> sender_runtime_args = {
+        packet_header_address,
+        source_l1_buffer_address,
+        packet_payload_size_bytes,
+        num_packets,
+        receiver_noc_encoding,
+        time_seed,
+        mcast_start_id.second,
+        mcast_start_id.first};
+
+    std::vector<uint32_t> mcast_header_rtas(4, 0);
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_header_rtas[static_cast<uint32_t>(
+            control_plane->routing_direction_to_eth_direction(routing_info.mcast_dir))] = routing_info.num_mcast_hops;
+    }
+    sender_runtime_args.insert(sender_runtime_args.end(), mcast_header_rtas.begin(), mcast_header_rtas.end());
+    // append the EDM connection rt args
+    append_fabric_connection_rt_args(
+        sender_phys_id, mcast_start_phys_id, 0, sender_program, {sender_logical_core}, sender_runtime_args);
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: aagarwalTT
body: Will the mcast start device always be adjacent to the sender device?
created_at: 2025-05-15T17:38:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +139,211 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto edm_config = get_tt_fabric_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
+    uint32_t source_l1_buffer_address = 0x30000;
+    uint32_t packet_payload_size_bytes = edm_config.topology == Topology::Mesh ? 2048 : 4096;
+    uint32_t num_packets = 100;
+    uint32_t test_results_address = 0x100000;
+    uint32_t test_results_size_bytes = 128;
+    uint32_t target_address = 0x30000;
+    uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
+
+    // common compile time args for sender and receiver
+    std::vector<uint32_t> compile_time_args = {test_results_address, test_results_size_bytes, target_address};
+
+    std::map<string, string> defines = {};
+    if (is_2d_fabric) {
+        defines["FABRIC_2D"] = "";
+    }
+
+    auto sender_program = tt_metal::CreateProgram();
+    auto sender_kernel = tt_metal::CreateKernel(
+        sender_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp",
+        {sender_logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args,
+            .defines = defines});
+
+    std::vector<uint32_t> sender_runtime_args = {
+        packet_header_address,
+        source_l1_buffer_address,
+        packet_payload_size_bytes,
+        num_packets,
+        receiver_noc_encoding,
+        time_seed,
+        mcast_start_id.second,
+        mcast_start_id.first};
+
+    std::vector<uint32_t> mcast_header_rtas(4, 0);
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_header_rtas[static_cast<uint32_t>(
+            control_plane->routing_direction_to_eth_direction(routing_info.mcast_dir))] = routing_info.num_mcast_hops;
+    }
+    sender_runtime_args.insert(sender_runtime_args.end(), mcast_header_rtas.begin(), mcast_header_rtas.end());
+    // append the EDM connection rt args
+    append_fabric_connection_rt_args(
+        sender_phys_id, mcast_start_phys_id, 0, sender_program, {sender_logical_core}, sender_runtime_args);
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: tt-asaigal
body: For these tests yes, since I derive the sender as a neighbour of the mcast start chip. We can augment the test the sender further away if we want. That will only be a legal config on Galaxy systems.
created_at: 2025-05-15T17:43:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +139,211 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto edm_config = get_tt_fabric_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
+    uint32_t source_l1_buffer_address = 0x30000;
+    uint32_t packet_payload_size_bytes = edm_config.topology == Topology::Mesh ? 2048 : 4096;
+    uint32_t num_packets = 100;
+    uint32_t test_results_address = 0x100000;
+    uint32_t test_results_size_bytes = 128;
+    uint32_t target_address = 0x30000;
+    uint32_t time_seed = std::chrono::system_clock::now().time_since_epoch().count();
+
+    // common compile time args for sender and receiver
+    std::vector<uint32_t> compile_time_args = {test_results_address, test_results_size_bytes, target_address};
+
+    std::map<string, string> defines = {};
+    if (is_2d_fabric) {
+        defines["FABRIC_2D"] = "";
+    }
+
+    auto sender_program = tt_metal::CreateProgram();
+    auto sender_kernel = tt_metal::CreateKernel(
+        sender_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_line_mcast_tx.cpp",
+        {sender_logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args,
+            .defines = defines});
+
+    std::vector<uint32_t> sender_runtime_args = {
+        packet_header_address,
+        source_l1_buffer_address,
+        packet_payload_size_bytes,
+        num_packets,
+        receiver_noc_encoding,
+        time_seed,
+        mcast_start_id.second,
+        mcast_start_id.first};
+
+    std::vector<uint32_t> mcast_header_rtas(4, 0);
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_header_rtas[static_cast<uint32_t>(
+            control_plane->routing_direction_to_eth_direction(routing_info.mcast_dir))] = routing_info.num_mcast_hops;
+    }
+    sender_runtime_args.insert(sender_runtime_args.end(), mcast_header_rtas.begin(), mcast_header_rtas.end());
+    // append the EDM connection rt args
+    append_fabric_connection_rt_args(
+        sender_phys_id, mcast_start_phys_id, 0, sender_program, {sender_logical_core}, sender_runtime_args);
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: aagarwalTT
body: Sounds good. Please note that `append_fabric_connection_rt_args` expects the src and dst chips to be adjacent.
created_at: 2025-05-15T17:47:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,7 +173,9 @@ constexpr size_t sender_4_completed_packet_header_cb_size_headers =
 constexpr size_t is_2d_fabric = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 56);
 constexpr size_t my_direction = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 57);
 
-constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 58;
+constexpr size_t num_eth_ports = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 58);
path: tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
user: login: ubcheema
body: Is this number (num_eth_ports) fixed? i.e 16 for WH, 12 for BH?

created_at: 2025-05-15T17:58:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,7 +173,9 @@ constexpr size_t sender_4_completed_packet_header_cb_size_headers =
 constexpr size_t is_2d_fabric = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 56);
 constexpr size_t my_direction = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 57);
 
-constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 58;
+constexpr size_t num_eth_ports = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 58);
path: tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
user: login: tt-asaigal
body: yes, this comes from the arch based constants in UMD. I made this a CTA primarily because I didn't want to hardcode based on arch in the kernel if we already have these lookups being done elsewhere.
created_at: 2025-05-15T18:00:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,7 +173,9 @@ constexpr size_t sender_4_completed_packet_header_cb_size_headers =
 constexpr size_t is_2d_fabric = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 56);
 constexpr size_t my_direction = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 57);
 
-constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 58;
+constexpr size_t num_eth_ports = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 58);
path: tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
user: login: ubcheema
body: Ok. Just wanted to make sure that this should be the max possible connected ethernet ports for an architecture.
Since we index into port_direction_table using the ethernet channel number, and we don't know which channels are actually connected, the array is sized to max possible channels.
created_at: 2025-05-15T19:08:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,7 +173,9 @@ constexpr size_t sender_4_completed_packet_header_cb_size_headers =
 constexpr size_t is_2d_fabric = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 56);
 constexpr size_t my_direction = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 57);
 
-constexpr size_t SPECIAL_MARKER_0_IDX = MAIN_CT_ARGS_START_IDX + 58;
+constexpr size_t num_eth_ports = get_compile_time_arg_val(MAIN_CT_ARGS_START_IDX + 58);
path: tt_metal/fabric/hw/inc/edm_fabric/1d_fabric_constants.hpp
user: login: tt-asaigal
body: Yeah this would be the max per arch. We only populate indices that are valid per chip.
created_at: 2025-05-15T19:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,11 +7,12 @@
 namespace tt::tt_metal {
 enum class FabricConfig : uint32_t {
     DISABLED = 0,
-    FABRIC_1D = 1,       // Instatiates fabric with 1D routing and no deadlock avoidance
-    FABRIC_1D_RING = 2,  // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
-    FABRIC_2D = 3,       // Instatiates fabric with 2D routing in pull mode
-    FABRIC_2D_PUSH = 4,  // Instatiates fabric with 2D routing in push mode
-    CUSTOM = 5
+    FABRIC_1D = 1,          // Instatiates fabric with 1D routing and no deadlock avoidance
+    FABRIC_1D_RING = 2,     // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
+    FABRIC_2D = 3,          // Instatiates fabric with 2D routing in pull mode
+    FABRIC_2D_PUSH = 4,     // Instatiates fabric with 2D routing in push mode
+    FABRIC_2D_DYNAMIC = 5,  // Instatiates fabric with 2D routing in push mode with dynamic routing
+    CUSTOM = 6
path: tt_metal/api/tt-metalium/fabric_types.hpp
user: login: afuller-TT
body: Is it okay to change the meaning of '5'?  This enum doesn't get serialized anywhere or cross a wire with different versions of Metal on either side?
created_at: 2025-05-15T19:23:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,11 +7,12 @@
 namespace tt::tt_metal {
 enum class FabricConfig : uint32_t {
     DISABLED = 0,
-    FABRIC_1D = 1,       // Instatiates fabric with 1D routing and no deadlock avoidance
-    FABRIC_1D_RING = 2,  // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
-    FABRIC_2D = 3,       // Instatiates fabric with 2D routing in pull mode
-    FABRIC_2D_PUSH = 4,  // Instatiates fabric with 2D routing in push mode
-    CUSTOM = 5
+    FABRIC_1D = 1,          // Instatiates fabric with 1D routing and no deadlock avoidance
+    FABRIC_1D_RING = 2,     // Instatiates fabric with 1D routing and with deadlock avoidance using datelines
+    FABRIC_2D = 3,          // Instatiates fabric with 2D routing in pull mode
+    FABRIC_2D_PUSH = 4,     // Instatiates fabric with 2D routing in push mode
+    FABRIC_2D_DYNAMIC = 5,  // Instatiates fabric with 2D routing in push mode with dynamic routing
+    CUSTOM = 6
path: tt_metal/api/tt-metalium/fabric_types.hpp
user: login: tt-asaigal
body: My expectation would be that:
1. Each side of the wire initializes fabric independently (I think this enum is used primarily for initialization)
2. Each instance of Metal would need to handshake with all other instances to ensure that versions match, or are compatible. We'll have an entire list of [problems otherwise.
created_at: 2025-05-15T19:32:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,12 +24,14 @@ namespace tt::tt_fabric {
 bool is_tt_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D ||
            fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D_RING ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
 }
 
 bool is_2d_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
path: tt_metal/fabric/fabric_host_utils.cpp
user: login: aagarwalTT
body: this may not be needed here. `is_2d_fabric_config` is only supposed to be for legacy (pull mode) code paths
created_at: 2025-05-15T20:07:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: aagarwalTT
body: Curious- do jump tables work for if-else as well?
created_at: 2025-05-15T20:11:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto& fabric_context = control_plane->get_fabric_context();
+    const auto topology = fabric_context.get_fabric_topology();
+    const auto& edm_config = fabric_context.get_fabric_router_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: abhullar-tt
body: a lot of other tests use hardcoded params but is it possible to derive these for new tests? BH mem map is different than WH and this seems to be causing some issues in porting for BH
created_at: 2025-05-15T20:11:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
+    tt_l1_ptr MeshPacketHeader* packet_header,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    if (packet_header->is_mcast_active) {
+        // mcast downstream needs to check if downstream has space (lookup from set direction field)
+        // forward to local and remote
+        bool has_space = true;
+        // If the current chip is part of an mcast group, stall until all downstream mcast receivers have
+        // space
+        for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+            if (packet_header->mcast_params[i] and i != my_direction) {
+                has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+            }
+        }
+        return has_space;
+    } else {
+        // check if header matches curr. If so, check mcast fields, set mcast true and forward to specific direction
+        auto dest_chip_id = packet_header->dst_start_chip_id;
+        auto dest_mesh_id = packet_header->dst_start_mesh_id;
+        volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: aagarwalTT
body: remove volatile?
created_at: 2025-05-15T20:11:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
+    tt_l1_ptr MeshPacketHeader* packet_header,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    if (packet_header->is_mcast_active) {
+        // mcast downstream needs to check if downstream has space (lookup from set direction field)
+        // forward to local and remote
+        bool has_space = true;
+        // If the current chip is part of an mcast group, stall until all downstream mcast receivers have
+        // space
+        for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+            if (packet_header->mcast_params[i] and i != my_direction) {
+                has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+            }
+        }
+        return has_space;
+    } else {
+        // check if header matches curr. If so, check mcast fields, set mcast true and forward to specific direction
+        auto dest_chip_id = packet_header->dst_start_chip_id;
+        auto dest_mesh_id = packet_header->dst_start_mesh_id;
+        volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
+            reinterpret_cast<tt_l1_ptr fabric_router_l1_config_t*>(eth_l1_mem::address_map::FABRIC_ROUTER_CONFIG_BASE);
+
+        if (dest_mesh_id != routing_table->my_mesh_id) {
+            uint32_t downstream_channel = routing_table->inter_mesh_table.dest_entry[dest_mesh_id];
+            auto downstream_direction = port_direction_table[downstream_channel];
+            return downstream_edm_interface[downstream_direction].edm_has_space_for_packet();
+        } else {
+            if (dest_chip_id == routing_table->my_device_id) {
+                // Packet has reached its intended chip. Check if this is an mcast or unicast txn.
+                // If mcast, this packet needs to be forwarded to remote and unicasted locally.
+                bool mcast_active = false;
+                bool has_space = true;
+                for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+                    if (packet_header->mcast_params[i]) {
+                        mcast_active = true;
+                        has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+                    }
+                }
+                packet_header->is_mcast_active =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: aagarwalTT
body: nit- update formatting?
created_at: 2025-05-15T20:14:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -548,6 +601,65 @@ FORCE_INLINE void receiver_forward_packet(
     }
 }
 
+#if defined(FABRIC_2D) && defined(DYNAMIC_ROUTING_ENABLED)
+// !!!WARNING!!! - MAKE SURE CONSUMER HAS SPACE BEFORE CALLING
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) void receiver_forward_packet(
+    tt_l1_ptr PACKET_HEADER_TYPE* packet_start,
+    ROUTING_FIELDS_TYPE cached_routing_fields,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    uint8_t transaction_id,
+    uint8_t rx_channel_id,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    auto dest_mesh_id = packet_start->dst_start_mesh_id;
+    auto dest_chip_id = packet_start->dst_start_chip_id;
+    auto mcast_active = packet_start->is_mcast_active;
+
+    uint16_t payload_size_bytes = packet_start->payload_size_bytes;
+    volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: aagarwalTT
body: remove volatile?
created_at: 2025-05-15T20:16:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: aagarwalTT
body: capture the return value and skip if no requested connections found?
created_at: 2025-05-15T20:49:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -137,7 +138,8 @@ FabricEriscDatamoverConfig::FabricEriscDatamoverConfig(std::size_t channel_buffe
         this->num_used_receiver_channels -= 1;
         this->num_fwd_paths -= 1;
     }
-    tt::tt_fabric::set_routing_mode(topology);
+    tt::tt_metal::FabricConfig fabric_config = tt::tt_metal::MetalContext::instance().get_cluster().get_fabric_config();
+    tt::tt_fabric::set_routing_mode(topology, fabric_config);
path: tt_metal/fabric/erisc_datamover_builder.cpp
user: login: aagarwalTT
body: Instead of fetching the config here, could we please set the routing mode here (https://github.com/tenstorrent/tt-metal/blob/main/tt_metal/fabric/fabric_context.cpp#L85) instead? 
created_at: 2025-05-15T21:23:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: daminakaTT
body: Can we stop copy&paste test codes?
created_at: 2025-05-15T21:58:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -24,12 +24,14 @@ namespace tt::tt_fabric {
 bool is_tt_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D ||
            fabric_config == tt::tt_metal::FabricConfig::FABRIC_1D_RING ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
 }
 
 bool is_2d_fabric_config(tt::tt_metal::FabricConfig fabric_config) {
     return fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D ||
-           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH;
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_PUSH ||
+           fabric_config == tt::tt_metal::FabricConfig::FABRIC_2D_DYNAMIC;
path: tt_metal/fabric/fabric_host_utils.cpp
user: login: tt-asaigal
body: Routing table init step in `DevicePool` relies on this query. If we remove the change here, routing tables won't get written for `FABRIC_2D_DYNAMIC`.

I think what we want to do in general is:
1. Deprecate/cleanup the use of pull based fabric
2. Cleanup code where this call is redundant (ex: `tt::tt_fabric::is_tt_fabric_config(fabric_config) || tt::tt_fabric::is_2d_fabric_config(fabric_config)`)
created_at: 2025-05-15T22:34:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: tt-asaigal
body: they don't as per my understanding, forgot to clean this up. Removed
created_at: 2025-05-15T23:01:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
+    tt_l1_ptr MeshPacketHeader* packet_header,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    if (packet_header->is_mcast_active) {
+        // mcast downstream needs to check if downstream has space (lookup from set direction field)
+        // forward to local and remote
+        bool has_space = true;
+        // If the current chip is part of an mcast group, stall until all downstream mcast receivers have
+        // space
+        for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+            if (packet_header->mcast_params[i] and i != my_direction) {
+                has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+            }
+        }
+        return has_space;
+    } else {
+        // check if header matches curr. If so, check mcast fields, set mcast true and forward to specific direction
+        auto dest_chip_id = packet_header->dst_start_chip_id;
+        auto dest_mesh_id = packet_header->dst_start_mesh_id;
+        volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: tt-asaigal
body: done
created_at: 2025-05-15T23:01:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -447,6 +447,59 @@ FORCE_INLINE bool can_forward_packet_completely(
     return deliver_locally_only || downstream_edm_interface.edm_has_space_for_packet();
 }
 
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) bool can_forward_packet_completely(
+    tt_l1_ptr MeshPacketHeader* packet_header,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    if (packet_header->is_mcast_active) {
+        // mcast downstream needs to check if downstream has space (lookup from set direction field)
+        // forward to local and remote
+        bool has_space = true;
+        // If the current chip is part of an mcast group, stall until all downstream mcast receivers have
+        // space
+        for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+            if (packet_header->mcast_params[i] and i != my_direction) {
+                has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+            }
+        }
+        return has_space;
+    } else {
+        // check if header matches curr. If so, check mcast fields, set mcast true and forward to specific direction
+        auto dest_chip_id = packet_header->dst_start_chip_id;
+        auto dest_mesh_id = packet_header->dst_start_mesh_id;
+        volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
+            reinterpret_cast<tt_l1_ptr fabric_router_l1_config_t*>(eth_l1_mem::address_map::FABRIC_ROUTER_CONFIG_BASE);
+
+        if (dest_mesh_id != routing_table->my_mesh_id) {
+            uint32_t downstream_channel = routing_table->inter_mesh_table.dest_entry[dest_mesh_id];
+            auto downstream_direction = port_direction_table[downstream_channel];
+            return downstream_edm_interface[downstream_direction].edm_has_space_for_packet();
+        } else {
+            if (dest_chip_id == routing_table->my_device_id) {
+                // Packet has reached its intended chip. Check if this is an mcast or unicast txn.
+                // If mcast, this packet needs to be forwarded to remote and unicasted locally.
+                bool mcast_active = false;
+                bool has_space = true;
+                for (size_t i = eth_chan_directions::EAST; i < eth_chan_directions::COUNT; i++) {
+                    if (packet_header->mcast_params[i]) {
+                        mcast_active = true;
+                        has_space &= downstream_edm_interface[i].edm_has_space_for_packet();
+                    }
+                }
+                packet_header->is_mcast_active =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: tt-asaigal
body: done
created_at: 2025-05-15T23:01:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -548,6 +601,65 @@ FORCE_INLINE void receiver_forward_packet(
     }
 }
 
+#if defined(FABRIC_2D) && defined(DYNAMIC_ROUTING_ENABLED)
+// !!!WARNING!!! - MAKE SURE CONSUMER HAS SPACE BEFORE CALLING
+template <uint8_t SENDER_NUM_BUFFERS>
+FORCE_INLINE __attribute__((optimize("jump-tables"))) void receiver_forward_packet(
+    tt_l1_ptr PACKET_HEADER_TYPE* packet_start,
+    ROUTING_FIELDS_TYPE cached_routing_fields,
+    std::array<tt::tt_fabric::EdmToEdmSender<SENDER_NUM_BUFFERS>, NUM_USED_RECEIVER_CHANNELS>& downstream_edm_interface,
+    uint8_t transaction_id,
+    uint8_t rx_channel_id,
+    std::array<uint8_t, num_eth_ports>& port_direction_table) {
+    auto dest_mesh_id = packet_start->dst_start_mesh_id;
+    auto dest_chip_id = packet_start->dst_start_chip_id;
+    auto mcast_active = packet_start->is_mcast_active;
+
+    uint16_t payload_size_bytes = packet_start->payload_size_bytes;
+    volatile tt_l1_ptr fabric_router_l1_config_t* routing_table =
path: tt_metal/fabric/impl/kernels/edm_fabric/fabric_erisc_datamover.cpp
user: login: tt-asaigal
body: done
created_at: 2025-05-15T23:01:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: tt-asaigal
body: done
created_at: 2025-05-15T23:01:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -137,7 +138,8 @@ FabricEriscDatamoverConfig::FabricEriscDatamoverConfig(std::size_t channel_buffe
         this->num_used_receiver_channels -= 1;
         this->num_fwd_paths -= 1;
     }
-    tt::tt_fabric::set_routing_mode(topology);
+    tt::tt_metal::FabricConfig fabric_config = tt::tt_metal::MetalContext::instance().get_cluster().get_fabric_config();
+    tt::tt_fabric::set_routing_mode(topology, fabric_config);
path: tt_metal/fabric/erisc_datamover_builder.cpp
user: login: tt-asaigal
body: done
created_at: 2025-05-15T23:01:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto& fabric_context = control_plane->get_fabric_context();
+    const auto topology = fabric_context.get_fabric_topology();
+    const auto& edm_config = fabric_context.get_fabric_router_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: tt-asaigal
body: I think the proper way of doing this is to just allocate buffers in L1 for each section of memory that needs to be reserved. Let me look into this
created_at: 2025-05-15T23:02:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto& fabric_context = control_plane->get_fabric_context();
+    const auto topology = fabric_context.get_fabric_topology();
+    const auto& edm_config = fabric_context.get_fabric_router_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: aagarwalTT
body: You can probably take a look at this for easy ref: https://github.com/tenstorrent/tt-metal/blob/main/tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_fabric_mux.cpp#L87
created_at: 2025-05-16T00:01:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
+    auto* control_plane = tt::tt_metal::MetalContext::instance().get_cluster().get_control_plane();
+    auto user_meshes = control_plane->get_user_physical_mesh_ids();
+    bool system_accomodates_mcast = false;
+    for (const auto& mesh : user_meshes) {
+        auto mesh_shape = control_plane->get_physical_mesh_shape(mesh);
+        // Need at least 8 chips for all mcast tests
+        if (mesh_shape.mesh_size() >= 8) {
+            system_accomodates_mcast = true;
+            break;
+        }
+    }
+    if (!system_accomodates_mcast) {
+        GTEST_SKIP() << "No mesh found for line mcast test";
+    }
+    // Setup mcast path
+    chip_id_t mcast_start_phys_id;                              // Physical ID for chip starting mcast
+    std::pair<mesh_id_t, chip_id_t> mcast_start_id;             // Mesh ID for chip starting mcast
+    std::unordered_map<RoutingDirection, uint32_t> mcast_hops;  // Specify mcast path from mcast src chip
+    std::unordered_map<RoutingDirection, std::vector<std::pair<mesh_id_t, chip_id_t>>>
+        mcast_group;  // Mesh IDs for chips involved in mcast
+    std::unordered_map<RoutingDirection, std::vector<chip_id_t>>
+        mcast_group_phys_ids_per_dir;  // Physical IDs for chips involved in mcast
+
+    for (const auto& routing_info : mcast_routing_info) {
+        mcast_hops[routing_info.mcast_dir] = routing_info.num_mcast_hops;
+    }
+
+    find_device_with_neighbor_in_multi_direction(
+        fixture,
+        mcast_start_id,
+        mcast_group,
+        mcast_start_phys_id,
+        mcast_group_phys_ids_per_dir,
+        mcast_hops,
+        unicast_dir);
+    // Compute coordinates of the remote chip that sends an mcast request to the mcast sender
+    std::pair<mesh_id_t, chip_id_t> sender_id = {
+        mcast_start_id.first,
+        control_plane->get_intra_chip_neighbors(mcast_start_id.first, mcast_start_id.second, unicast_dir)[0]};
+    auto sender_phys_id = control_plane->get_physical_chip_id_from_mesh_chip_id(sender_id);
+    // Compute physical IDs for mcast group chips
+    std::vector<chip_id_t> mcast_group_phys_ids = {};
+    for (const auto& routing_info : mcast_routing_info) {
+        for (auto phys_id : mcast_group_phys_ids_per_dir[routing_info.mcast_dir]) {
+            mcast_group_phys_ids.push_back(phys_id);
+        }
+    }
+
+    CoreCoord sender_logical_core = {0, 0};    // This core on the sender (remote chip) will make the mcast request
+    CoreCoord receiver_logical_core = {1, 0};  // Data will be forwarded to this core on al chips in the mcast group
+
+    const auto& fabric_context = control_plane->get_fabric_context();
+    const auto topology = fabric_context.get_fabric_topology();
+    const auto& edm_config = fabric_context.get_fabric_router_config();
+    uint32_t is_2d_fabric = edm_config.topology == Topology::Mesh;
+
+    auto routers = control_plane->get_routers_to_chip(
+        sender_id.first, sender_id.second, mcast_start_id.first, mcast_start_id.second);
+    if (routers.size() == 0) {
+        log_info(
+            tt::LogTest,
+            "No fabric routers between Src MeshId {} ChipId {} - Dst MeshId {} ChipId {}",
+            sender_id.first,
+            sender_id.second,
+            mcast_start_id.first,
+            mcast_start_id.second);
+
+        GTEST_SKIP() << "Skipping Test";
+    }
+
+    auto* sender_device = DevicePool::instance().get_active_device(sender_phys_id);
+    auto* mcast_start_device = DevicePool::instance().get_active_device(mcast_start_phys_id);
+    std::vector<tt_metal::IDevice*> mcast_group_devices = {};
+    for (auto id : mcast_group_phys_ids) {
+        mcast_group_devices.push_back(DevicePool::instance().get_active_device(id));
+    }
+
+    CoreCoord sender_virtual_core = sender_device->worker_core_from_logical_core(sender_logical_core);
+    CoreCoord receiver_virtual_core = mcast_start_device->worker_core_from_logical_core(receiver_logical_core);
+
+    auto receiver_noc_encoding =
+        tt::tt_metal::MetalContext::instance().hal().noc_xy_encoding(receiver_virtual_core.x, receiver_virtual_core.y);
+
+    auto mesh_shape = control_plane->get_physical_mesh_shape(sender_id.first);
+
+    uint32_t packet_header_address = 0x25000;
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: tt-asaigal
body: Actually the easier approach was to just use the allocator to find a legal address space, so I just went with that. See `compute_test_addrs` in `tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp`.

Reserved sizes for each memory region are unchanged with this commit.
created_at: 2025-05-16T00:02:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,6 +141,213 @@ bool find_device_with_neighbor_in_multi_direction(
     return connection_found;
 }
 
+std::shared_ptr<tt_metal::Program> create_receiver_program(
+    const std::vector<uint32_t>& compile_time_args,
+    const std::vector<uint32_t>& runtime_args,
+    const CoreCoord& logical_core) {
+    auto recv_program = std::make_shared<tt_metal::Program>();
+    auto recv_kernel = tt_metal::CreateKernel(
+        *recv_program,
+        "tests/tt_metal/tt_metal/perf_microbenchmark/routing/kernels/tt_fabric_1d_rx.cpp",
+        {logical_core},
+        tt_metal::DataMovementConfig{
+            .processor = tt_metal::DataMovementProcessor::RISCV_0,
+            .noc = tt_metal::NOC::RISCV_0_default,
+            .compile_args = compile_time_args});
+    tt_metal::SetRuntimeArgs(*recv_program, recv_kernel, logical_core, runtime_args);
+    return recv_program;
+}
+
+void RunTestLineMcast(
+    BaseFabricFixture* fixture, RoutingDirection unicast_dir, const std::vector<McastRoutingInfo>& mcast_routing_info) {
path: tests/tt_metal/tt_fabric/fabric_data_movement/test_basic_1d_fabric.cpp
user: login: tt-asaigal
body: In the latest commit, I've de-duplicated some of the test/address setup code. There's a fair amount of further cleanup that can be done (compile time arg, runtime arg setup, for example). I'll do this in a follow up PR targeting all fabric tests. 
created_at: 2025-05-16T00:06:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: tt-asaigal
created_at: 2025-05-15T16:55:24Z
body: Metal Ubench Pipeline: https://github.com/tenstorrent/tt-metal/actions/runs/15034269657

Failures are the same as main. Mux regression was caused by a recent commit to main.
author_association: CONTRIBUTOR
user: login: tt-asaigal
created_at: 2025-05-15T18:28:28Z
body: Passing T3K Unit Tests: https://github.com/tenstorrent/tt-metal/actions/runs/15050768393
TG Unit: https://github.com/tenstorrent/tt-metal/actions/runs/15050747294
author_association: CONTRIBUTOR
user: login: ubcheema
created_at: 2025-05-15T21:41:39Z
body: @tt-asaigal Please verify on TG as well.

author_association: CONTRIBUTOR
user: login: tt-asaigal
created_at: 2025-05-15T21:55:19Z
body: > @tt-asaigal Please verify on TG as well.

TG from a few hours ago, waiting for the job to get picked up.
https://github.com/tenstorrent/tt-metal/actions/runs/15053671485/job/42314337009
author_association: CONTRIBUTOR
user: login: tt-asaigal
created_at: 2025-05-16T00:08:01Z
body: > > @tt-asaigal Please verify on TG as well.
> 
> TG from a few hours ago, waiting for the job to get picked up. https://github.com/tenstorrent/tt-metal/actions/runs/15053671485/job/42314337009

fyi @ubcheema the linked run passes. I think dynamic routing should be fine on TG.
author_association: CONTRIBUTOR