137 lines
6.6 KiB
C
137 lines
6.6 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* @file ft_tt_op_replicas.h
|
|
* @brief The HOST half of the resident replica table — the map that tells a writer kernel
|
|
* which of its output strips are mirrored into other DRAM banks, and where.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* WHAT PROBLEM THIS SOLVES, IN ONE PARAGRAPH
|
|
* ---------------------------------------------------------------------------
|
|
* The Blackhole's 510.8 GB/s is EIGHT banks of ~59 GB/s. An activation strip is allocated
|
|
* in one bank, and every member of a column-split matmul reads the WHOLE strip — so a
|
|
* 16-member projection puts sixteen readers on one bank. Measured on Ornith during decode:
|
|
* bank 2 at 43.1 GB/s (73% of its own ceiling) with the other seven under 5, and the card
|
|
* at 16% of peak. That is why matmul card work is flat from 4 members to 16 and why the
|
|
* shipped setting is four cores on a 140-Tensix part.
|
|
*
|
|
* The fix is to give the strip more than one bank. The DESCRIPTOR side is free — dispatch
|
|
* already writes a separate parameter block per member, so member `m` naming replica
|
|
* `m % replicas` is an index, not a protocol change. The cost lands on the PRODUCER, and
|
|
* this file is how the producer is told.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* WHY IT IS A RESIDENT TABLE AND NOT DESCRIPTOR WORDS
|
|
* ---------------------------------------------------------------------------
|
|
* `FT_TT_QUEUE_PARAM_WORDS` is 16 for every family and a seventeenth changes the shared
|
|
* slot stride. The REDUCE family already spends words 13/14/15 on its program, and reduce
|
|
* is exactly the producer that must fan out (`normed` is a reduce output). So there is no
|
|
* free word to put a destination list in, for the one family that needs it most.
|
|
*
|
|
* The table is therefore keyed on the destination the descriptor ALREADY carries. The
|
|
* writer looks up `(kVecCX, kVecCY, kVecCBase)`; a hit names the extra destinations, a
|
|
* miss is the shipped path word for word. An empty table misses everything, which is what
|
|
* makes "off" the default and the null arm the same object.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* WHAT THIS FILE REFUSES, AND WHY EACH REFUSAL IS A WRONG ANSWER OTHERWISE
|
|
* ---------------------------------------------------------------------------
|
|
* - a set with fewer than two destinations — it would cost a table entry and mean nothing
|
|
* - two destinations that are the same (x, y, base) — the same bytes written twice, which
|
|
* reads as a working replica map and delivers one bank
|
|
* - two SETS with the same primary — the writer takes the first hit, so the second set
|
|
* would be silently dead
|
|
* - more sets or destinations than the wire format holds
|
|
*
|
|
* None of these can be seen on the card: a bad map produces a strip that is stale in one
|
|
* bank, and a matmul member reading it returns a plausible wrong answer.
|
|
*
|
|
* @status Written 2026-08-19 for the A-bank spread. Pinned off-hardware by
|
|
* tests/test_replica_table.c, which exercises the KERNEL's own lookup.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#ifndef FT_TT_OP_REPLICAS_H
|
|
#define FT_TT_OP_REPLICAS_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "ft_tt_launch.h"
|
|
#include "kernels/resident_output_replicas.h"
|
|
|
|
/** One place a tile is written: a DRAM bank's NoC coordinate and a byte offset in it. */
|
|
typedef struct ft_tt_op_replica_destination {
|
|
uint16_t x, y;
|
|
uint32_t base;
|
|
} ft_tt_op_replica_destination;
|
|
|
|
/** One strip and its mirrors. `destination[0]` is the PRIMARY and is the lookup key — the
|
|
* address every other consumer of the strip already uses, which is what keeps replication
|
|
* invisible to everything but the matmul's A side. */
|
|
typedef struct ft_tt_op_replica_set {
|
|
uint32_t count;
|
|
ft_tt_op_replica_destination destination[FT_TT_REPLICA_MAX_DESTINATIONS];
|
|
} ft_tt_op_replica_set;
|
|
|
|
/** Everything one residency core needs to know. Zeroed is a valid, empty table. */
|
|
typedef struct ft_tt_op_replica_table {
|
|
uint32_t set_count;
|
|
ft_tt_op_replica_set set[FT_TT_REPLICA_MAX_SETS];
|
|
} ft_tt_op_replica_table;
|
|
|
|
/**
|
|
* Encode a table into the words the kernel reads.
|
|
*
|
|
* Pure: no device access, no allocation — which is what lets the wire format be tested
|
|
* where a wrong word costs a second instead of a card session.
|
|
*
|
|
* A table with `set_count == 0` still encodes, with the magic present and the count zero.
|
|
* That is deliberately not the same as an unwritten region: it says "this core has been
|
|
* told, and the answer is nothing", which is the state a run with replication disabled
|
|
* should be able to assert.
|
|
*
|
|
* @param table the map; every field read
|
|
* @param out_words FT_TT_REPLICA_TABLE_WORDS values, fully written
|
|
* @return FT_TT_OK, or FT_TT_EINVAL with the reason on stderr
|
|
*
|
|
* Thread-safe: yes Reentrant: yes Signal-safe: no (it may print)
|
|
*/
|
|
int ft_tt_op_replica_table_encode(const ft_tt_op_replica_table *table,
|
|
uint32_t out_words[FT_TT_REPLICA_TABLE_WORDS]);
|
|
|
|
/**
|
|
* Encode and write the table into one core's L1, at FT_TT_REPLICA_TABLE_ADDRESS.
|
|
*
|
|
* ONE NoC write of the whole block. The region is inside the kernel-config region every
|
|
* program already owns, so this needs no arena, no circular buffer and no change to the
|
|
* L1 map — see kernels/resident_output_replicas.h for where the gap is.
|
|
*
|
|
* MUST be written before the first descriptor that could hit it, and it is safe to write
|
|
* again between descriptors: the writer reads it once per descriptor and never caches it.
|
|
*
|
|
* @return FT_TT_OK, or the NoC error, or FT_TT_EINVAL
|
|
*
|
|
* Thread-safe: no Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_op_replica_table_write(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
|
|
const ft_tt_op_replica_table *table);
|
|
|
|
/**
|
|
* Read one core's table back and compare it, word for word, against what `table` encodes.
|
|
*
|
|
* THIS IS THE ONLY THING THAT CAN CATCH A HALF-WRITTEN MAP. A replica whose bytes were
|
|
* never written is a strip that is stale in one bank, and the consumer that reads it
|
|
* returns a plausible wrong answer — there is no refusal anywhere in the path. So the map
|
|
* is verified by reading it back rather than by trusting the write's return code.
|
|
*
|
|
* @return FT_TT_OK when they match, FT_TT_ERROR with the first differing word on stderr,
|
|
* or the NoC error
|
|
*
|
|
* Thread-safe: no Reentrant: no Signal-safe: no
|
|
*/
|
|
int ft_tt_op_replica_table_verify(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
|
|
const ft_tt_op_replica_table *table);
|
|
|
|
#endif /* FT_TT_OP_REPLICAS_H */
|