271 lines
11 KiB
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;
|
|
}
|