Foundry/tt/ft_tt_multicast.c

271 lines
11 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_multicast.c
* @brief Implementation of ft_tt_multicast.h — see that header for why each refusal
* exists. Nothing here touches the device.
*/
#include "ft_tt_multicast.h"
#include "ft_tt_device.h"
/* The encoding, transliterated from
* the NoC multicast-address layout (the measured encoding in test_multicast_rectangle.c).
* Kept as file-private constants rather than reaching into the kernel header, because
* this file must build on the Mac where no tt-metal and no Tensix headers exist. */
#define FT_TT_MULTICAST_LOCAL_BITS 36u
#define FT_TT_MULTICAST_NODE_ID_BITS 6u
/** L1 is 1536 KiB; ft_tt_noc.h's ft_tt_noc_space_size() says the same thing for the
* transfer path. Named here so this file has no link dependency on the NoC module. */
#define FT_TT_MULTICAST_L1_BYTES (1536u * 1024u)
/* ------------------------------------------------------------------------- */
static bool coordinate_fits(ft_tt_noc_coord coord)
{
return coord.x <= FT_TT_MULTICAST_MAX_COORDINATE &&
coord.y <= FT_TT_MULTICAST_MAX_COORDINATE;
}
static void fill_counts(ft_tt_multicast_rectangle *rectangle, ft_tt_noc_coord sender)
{
rectangle->node_count = (rectangle->x_high - rectangle->x_low + 1u) *
(rectangle->y_high - rectangle->y_low + 1u);
rectangle->sender_inside = ft_tt_multicast_rectangle_contains(rectangle, sender);
rectangle->destination_count =
rectangle->sender_inside ? rectangle->node_count - 1u : rectangle->node_count;
}
/* ------------------------------------------------------------------------- */
bool ft_tt_multicast_rectangle_contains(const ft_tt_multicast_rectangle *rectangle,
ft_tt_noc_coord coord)
{
if (rectangle == NULL)
return false;
return coord.x >= rectangle->x_low && coord.x <= rectangle->x_high &&
coord.y >= rectangle->y_low && coord.y <= rectangle->y_high;
}
int ft_tt_multicast_check_rectangle(const ft_tt_multicast_rectangle *rectangle)
{
uint32_t nodes;
if (rectangle == NULL) {
ft_tt_log_error("multicast: NULL rectangle");
return FT_TT_EINVAL;
}
if (rectangle->x_low > rectangle->x_high || rectangle->y_low > rectangle->y_high) {
ft_tt_log_error("multicast: inverted rectangle (%u,%u)..(%u,%u) — the hardware does "
"not validate the corners, it broadcasts to whatever they describe",
rectangle->x_low, rectangle->y_low, rectangle->x_high,
rectangle->y_high);
return FT_TT_EINVAL;
}
if (rectangle->x_high > FT_TT_MULTICAST_MAX_COORDINATE ||
rectangle->y_high > FT_TT_MULTICAST_MAX_COORDINATE) {
ft_tt_log_error("multicast: corner (%u,%u) is above the 6-bit node-id field; it "
"would truncate onto a DIFFERENT live endpoint",
rectangle->x_high, rectangle->y_high);
return FT_TT_EINVAL;
}
nodes = (rectangle->x_high - rectangle->x_low + 1u) *
(rectangle->y_high - rectangle->y_low + 1u);
if (rectangle->node_count != nodes) {
ft_tt_log_error("multicast: node_count %u does not match the rectangle's %u nodes",
rectangle->node_count, nodes);
return FT_TT_EINVAL;
}
if (rectangle->destination_count !=
(rectangle->sender_inside ? nodes - 1u : nodes)) {
ft_tt_log_error("multicast: destination_count %u disagrees with %u nodes and a "
"sender that is %s the rectangle — the write barrier's accounting "
"is this number",
rectangle->destination_count, nodes,
rectangle->sender_inside ? "inside" : "outside");
return FT_TT_EINVAL;
}
if (rectangle->destination_count == 0u) {
ft_tt_log_error("multicast: the rectangle leaves ZERO destinations. A multicast to "
"nobody is the zero-length NoC transfer in different clothes and it "
"is refused, not optimised away (DEV_NOTES.md §1)");
return FT_TT_ERROR;
}
return FT_TT_OK;
}
int ft_tt_multicast_rectangle_from_corners(uint32_t x_low, uint32_t y_low, uint32_t x_high,
uint32_t y_high, ft_tt_noc_coord sender,
ft_tt_multicast_rectangle *out)
{
ft_tt_multicast_rectangle rectangle;
if (out == NULL) {
ft_tt_log_error("multicast: NULL out");
return FT_TT_EINVAL;
}
rectangle.x_low = x_low;
rectangle.y_low = y_low;
rectangle.x_high = x_high;
rectangle.y_high = y_high;
rectangle.node_count = 0u;
rectangle.destination_count = 0u;
rectangle.sender_inside = false;
/* fill_counts() would compute a nonsense span from an inverted or oversized pair, so
* the corners are checked BEFORE it runs. `out` is left at the zeroed counts on a
* refusal, which ft_tt_multicast_check_rectangle() also refuses, so a caller that
* ignores the return code still cannot launch with it. */
if (x_low > x_high || y_low > y_high ||
x_high > FT_TT_MULTICAST_MAX_COORDINATE || y_high > FT_TT_MULTICAST_MAX_COORDINATE) {
*out = rectangle;
return ft_tt_multicast_check_rectangle(&rectangle);
}
fill_counts(&rectangle, sender);
*out = rectangle;
return ft_tt_multicast_check_rectangle(&rectangle);
}
int ft_tt_multicast_rectangle_from_cores(const ft_tt_noc_coord *members, uint32_t count,
ft_tt_noc_coord sender,
ft_tt_multicast_rectangle *out)
{
ft_tt_multicast_rectangle rectangle;
uint32_t index;
uint32_t other;
if (members == NULL || out == NULL || count == 0u) {
ft_tt_log_error("multicast: NULL members/out or a zero member count");
return FT_TT_EINVAL;
}
if (!coordinate_fits(sender)) {
ft_tt_log_error("multicast: sender (%u,%u) is above the 6-bit node-id field",
sender.x, sender.y);
return FT_TT_EINVAL;
}
rectangle.x_low = members[0].x;
rectangle.x_high = members[0].x;
rectangle.y_low = members[0].y;
rectangle.y_high = members[0].y;
for (index = 0u; index < count; index++) {
if (!coordinate_fits(members[index])) {
ft_tt_log_error("multicast: member %u at (%u,%u) is above the 6-bit node-id "
"field; it would truncate onto a DIFFERENT live endpoint",
index, members[index].x, members[index].y);
return FT_TT_EINVAL;
}
/* Distinctness is checked pairwise rather than assumed, because the fill test
* below is a COUNT and a duplicated member would let a non-rectangle pass it.
* This is the same "check the N units against EACH OTHER" rule that caught the
* multicore seed collision (DEV_NOTES.md §3). */
for (other = 0u; other < index; other++) {
if (members[other].x == members[index].x && members[other].y == members[index].y) {
ft_tt_log_error("multicast: members %u and %u are both (%u,%u); a duplicate "
"would let a set that is NOT a rectangle pass the fill check",
other, index, members[index].x, members[index].y);
return FT_TT_EINVAL;
}
}
if (members[index].x < rectangle.x_low)
rectangle.x_low = members[index].x;
if (members[index].x > rectangle.x_high)
rectangle.x_high = members[index].x;
if (members[index].y < rectangle.y_low)
rectangle.y_low = members[index].y;
if (members[index].y > rectangle.y_high)
rectangle.y_high = members[index].y;
}
fill_counts(&rectangle, sender);
/* The fill test. Distinct members whose cardinality equals the bounding box's IS the
* box, by pigeonhole — so this needs no grid and cannot be fooled by a harvested
* column, which simply makes the box bigger than the set. */
if (count != rectangle.node_count) {
ft_tt_log_error("multicast: %u cores do not FILL their bounding box (%u,%u)..(%u,%u), "
"which holds %u nodes. A multicast writes to every node in the box, "
"so this would write into %u core(s) that are not in the set",
count, rectangle.x_low, rectangle.y_low, rectangle.x_high,
rectangle.y_high, rectangle.node_count, rectangle.node_count - count);
return FT_TT_ERROR;
}
*out = rectangle;
return ft_tt_multicast_check_rectangle(&rectangle);
}
int ft_tt_multicast_check_payload(uint32_t l1_address, size_t bytes)
{
if (bytes == 0u) {
ft_tt_log_error("multicast: a zero-length payload is refused. A zero-length NoC "
"transfer is what took this card to `Mem- BusMaster-` on 2026-08-12 "
"(DEV_NOTES.md §1)");
return FT_TT_EINVAL;
}
if ((l1_address % FT_TT_MULTICAST_ALIGNMENT_BYTES) != 0u) {
ft_tt_log_error("multicast: L1 address 0x%08x is not %u-byte aligned", l1_address,
FT_TT_MULTICAST_ALIGNMENT_BYTES);
return FT_TT_EINVAL;
}
if ((bytes % FT_TT_MULTICAST_ALIGNMENT_BYTES) != 0u) {
ft_tt_log_error("multicast: length %zu is not a multiple of %u", bytes,
FT_TT_MULTICAST_ALIGNMENT_BYTES);
return FT_TT_EINVAL;
}
if ((uint64_t)l1_address + (uint64_t)bytes > (uint64_t)FT_TT_MULTICAST_L1_BYTES) {
ft_tt_log_error("multicast: 0x%08x + %zu leaves L1 (%u bytes)", l1_address, bytes,
FT_TT_MULTICAST_L1_BYTES);
return FT_TT_EINVAL;
}
return FT_TT_OK;
}
int ft_tt_multicast_encode_address(const ft_tt_multicast_rectangle *rectangle,
uint32_t noc_index, uint32_t l1_address,
uint64_t *out_address)
{
uint32_t start_x;
uint32_t start_y;
uint32_t end_x;
uint32_t end_y;
int rc;
if (out_address == NULL) {
ft_tt_log_error("multicast: NULL out_address");
return FT_TT_EINVAL;
}
if (noc_index > 1u) {
ft_tt_log_error("multicast: NoC index %u; this part has two", noc_index);
return FT_TT_EINVAL;
}
rc = ft_tt_multicast_check_rectangle(rectangle);
if (rc != FT_TT_OK)
return rc;
/* NoC 0 takes the LOW corner as "start"; NoC 1 broadcasts the other way and takes them
* reversed. */
if (noc_index == 0u) {
start_x = rectangle->x_low;
start_y = rectangle->y_low;
end_x = rectangle->x_high;
end_y = rectangle->y_high;
} else {
start_x = rectangle->x_high;
start_y = rectangle->y_high;
end_x = rectangle->x_low;
end_y = rectangle->y_low;
}
*out_address =
((uint64_t)start_x << (FT_TT_MULTICAST_LOCAL_BITS + 2u * FT_TT_MULTICAST_NODE_ID_BITS)) |
((uint64_t)start_y << (FT_TT_MULTICAST_LOCAL_BITS + 3u * FT_TT_MULTICAST_NODE_ID_BITS)) |
((uint64_t)end_x << FT_TT_MULTICAST_LOCAL_BITS) |
((uint64_t)end_y << (FT_TT_MULTICAST_LOCAL_BITS + FT_TT_MULTICAST_NODE_ID_BITS)) |
(uint64_t)l1_address;
return FT_TT_OK;
}