/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file test_replica_table.c * @brief The resident replica table, off hardware — the wire format, the refusals, and the * KERNEL'S OWN LOOKUP run against the host's own encoding. * * --------------------------------------------------------------------------- * WHAT THIS SUITE IS FOR * --------------------------------------------------------------------------- * A replica map has no failure mode the card can report. If a destination is wrong, the * writer writes a tile somewhere harmless and one bank's copy of the strip goes stale; the * matmul member that reads that bank then returns a plausible wrong answer with every * kernel completing, every residency healthy and no error word set. So the map is checked * where checking is cheap. * * THE LOOKUP UNDER TEST IS THE KERNEL'S. `ftr_resolve_destinations()` is compiled into * `resident_vector_writer.c` from the same header this file includes, so a change to the * kernel's search changes this suite's answers. That is the point — the alternative is a * host model of the kernel, which is a second source of truth and has cost this project a * round already. * * --------------------------------------------------------------------------- * THE RED ARMS, AND WHAT EACH WOULD HAVE COST ON SILICON * --------------------------------------------------------------------------- * 1. NO MAGIC — a region nobody wrote must resolve to one destination, not to whatever the * L1 held. Red: the lookup reads the count anyway and fans a tile out to garbage NoC * coordinates. * 2. A PARTIAL KEY — matching on the bank alone. Red: any descriptor whose output shares a * bank with a replicated strip is fanned out over the replicated strip's addresses, * which corrupts an unrelated buffer. * 3. A DUPLICATE DESTINATION — encode must refuse. Red: the map looks armed, the writes * land twice in one bank, and the whole mechanism delivers one bank's bandwidth while * reporting R. * * make -f Makefile.replicatable run * * @status Written 2026-08-19 with the A-bank spread. * @issues None * @todo None */ #include #include #include "ft_tt_op_replicas.h" #include "ft_tt_test_report.h" /** A table with one four-way set, so every field has a distinct value. */ static void build_reference_table(ft_tt_op_replica_table *table) { uint32_t index; memset(table, 0, sizeof(*table)); table->set_count = 1u; table->set[0].count = 4u; for (index = 0u; index < 4u; index++) { table->set[0].destination[index].x = (uint16_t)(10u + index); table->set[0].destination[index].y = (uint16_t)(20u + index); table->set[0].destination[index].base = 0x100000u * (index + 1u); } } static void check_encode_round_trip(void) { uint32_t words[FT_TT_REPLICA_TABLE_WORDS]; uint32_t out_x[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_y[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_base[FT_TT_REPLICA_MAX_DESTINATIONS]; ft_tt_op_replica_table table; uint32_t found, index; bool all = true; ft_tt_test_step("encode, then resolve with the KERNEL's own lookup"); build_reference_table(&table); ft_tt_test_check_fmt("a four-way set encodes", ft_tt_op_replica_table_encode(&table, words) == 0, "%u words", (unsigned)FT_TT_REPLICA_TABLE_WORDS); ft_tt_test_check_fmt("word 0 is the magic", words[0] == FT_TT_REPLICA_TABLE_MAGIC, "0x%08x", words[0]); ft_tt_test_check_fmt("word 1 is the set count", words[1] == 1u, "%u", words[1]); found = ftr_resolve_destinations(words, table.set[0].destination[0].x, table.set[0].destination[0].y, table.set[0].destination[0].base, out_x, out_y, out_base); ft_tt_test_check_fmt("the primary resolves to all four destinations", found == 4u, "%u", found); for (index = 0u; index < found && index < 4u; index++) all = all && out_x[index] == table.set[0].destination[index].x && out_y[index] == table.set[0].destination[index].y && out_base[index] == table.set[0].destination[index].base; ft_tt_test_check("every destination comes back in order, x, y and base", all, "all four"); /* A DESTINATION THAT IS NOT THE PRIMARY IS NOT A KEY. Otherwise a replica's own address * would fan out again — the same tile written R times to R^2 places, and the second * fan-out reads a record whose primary is somebody else's mirror. */ found = ftr_resolve_destinations(words, table.set[0].destination[1].x, table.set[0].destination[1].y, table.set[0].destination[1].base, out_x, out_y, out_base); ft_tt_test_check_fmt("a MIRROR's own address is not a key", found == 1u, "%u", found); } static void check_miss_is_the_shipped_path(void) { uint32_t words[FT_TT_REPLICA_TABLE_WORDS]; uint32_t out_x[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_y[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_base[FT_TT_REPLICA_MAX_DESTINATIONS]; ft_tt_op_replica_table table; uint32_t found; ft_tt_test_step("a miss is exactly one destination — the shipped path"); build_reference_table(&table); (void)ft_tt_op_replica_table_encode(&table, words); found = ftr_resolve_destinations(words, 99u, 99u, 0x777000u, out_x, out_y, out_base); ft_tt_test_check_fmt("an unrelated destination resolves to itself alone", found == 1u, "%u", found); ft_tt_test_check_fmt("and it is unchanged", out_x[0] == 99u && out_y[0] == 99u && out_base[0] == 0x777000u, "(%u,%u)+0x%x", out_x[0], out_y[0], out_base[0]); /* RED ARM 2, as a positive check: the key is ALL THREE WORDS. Same bank, different * offset must MISS. A bank-only key would fan this out over the replicated strip's * addresses and corrupt a buffer that has nothing to do with the map. */ found = ftr_resolve_destinations(words, table.set[0].destination[0].x, table.set[0].destination[0].y, table.set[0].destination[0].base + 0x1000u, out_x, out_y, out_base); ft_tt_test_check_fmt("the same bank at a different offset MISSES", found == 1u, "%u", found); } static void check_empty_and_absent(void) { uint32_t words[FT_TT_REPLICA_TABLE_WORDS]; uint32_t garbage[FT_TT_REPLICA_TABLE_WORDS]; uint32_t out_x[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_y[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_base[FT_TT_REPLICA_MAX_DESTINATIONS]; ft_tt_op_replica_table empty; uint32_t index; ft_tt_test_step("the OFF state — an empty table and an unwritten region"); memset(&empty, 0, sizeof(empty)); ft_tt_test_check("an empty table encodes", ft_tt_op_replica_table_encode(&empty, words) == 0, "set_count 0"); ft_tt_test_check_fmt("and every destination misses it", ftr_resolve_destinations(words, 3u, 4u, 0x5000u, out_x, out_y, out_base) == 1u, "1 destination"); /* RED ARM 1, AND IT HAD TO BE SHARPENED TO DISCRIMINATE. The first version of this arm * filled the region with 0xDEADBEEF and passed with the magic check DELETED — the set * count read 0xDEADBEEF, which the range guard rejects anyway, so it tested the guard * and not the magic. The region that actually needs the magic is one holding a * STRUCTURALLY VALID table nobody meant to arm: the residue of an earlier run, or the * same L1 words another program used for something else. Here it is, built by hand, and * removing the magic check makes this check fail. */ for (index = 0u; index < FT_TT_REPLICA_TABLE_WORDS; index++) garbage[index] = 0u; garbage[1] = 1u; /* one set */ garbage[ftr_record_offset(0u)] = 2u; /* of two destinations */ garbage[ftr_record_offset(0u) + 1u] = 3u; /* whose primary is exactly the query */ garbage[ftr_record_offset(0u) + 2u] = 4u; garbage[ftr_record_offset(0u) + 3u] = 0x5000u; garbage[ftr_record_offset(0u) + 4u] = 31u; /* and whose mirror is not DRAM at all */ garbage[ftr_record_offset(0u) + 5u] = 31u; garbage[ftr_record_offset(0u) + 6u] = 0x9000u; ft_tt_test_check_fmt("a VALID-LOOKING region with no magic resolves to ONE destination", ftr_resolve_destinations(garbage, 3u, 4u, 0x5000u, out_x, out_y, out_base) == 1u, "magic 0x%08x is not 0x%08x", garbage[0], FT_TT_REPLICA_TABLE_MAGIC); ft_tt_test_check_fmt("...and the SAME words WITH the magic would have fanned out", (garbage[0] = FT_TT_REPLICA_TABLE_MAGIC, ftr_resolve_destinations(garbage, 3u, 4u, 0x5000u, out_x, out_y, out_base)) == 2u, "so the magic is what refused it"); memset(garbage, 0, sizeof(garbage)); ft_tt_test_check("a ZEROED region resolves to ONE destination", ftr_resolve_destinations(garbage, 3u, 4u, 0x5000u, out_x, out_y, out_base) == 1u, "no magic"); ft_tt_test_check("a NULL table resolves to ONE destination", ftr_resolve_destinations(NULL, 3u, 4u, 0x5000u, out_x, out_y, out_base) == 1u, "no table at all"); } static void check_refusals(void) { uint32_t words[FT_TT_REPLICA_TABLE_WORDS]; ft_tt_op_replica_table table; ft_tt_test_step("the four refusals, each of which is a silent wrong answer otherwise"); build_reference_table(&table); table.set[0].count = 1u; ft_tt_test_check("a one-destination set is refused", ft_tt_op_replica_table_encode(&table, words) != 0, "count 1"); build_reference_table(&table); table.set[0].count = FT_TT_REPLICA_MAX_DESTINATIONS + 1u; ft_tt_test_check("more destinations than the format holds is refused", ft_tt_op_replica_table_encode(&table, words) != 0, "count 9"); /* RED ARM 3. This is the bank-cursor bug: two "replicas" that are one bank. */ build_reference_table(&table); table.set[0].destination[2] = table.set[0].destination[1]; ft_tt_test_check("a DUPLICATE destination is refused", ft_tt_op_replica_table_encode(&table, words) != 0, "two of one bank"); build_reference_table(&table); table.set_count = 2u; table.set[1] = table.set[0]; ft_tt_test_check("two sets with the same PRIMARY are refused", ft_tt_op_replica_table_encode(&table, words) != 0, "a dead second set"); build_reference_table(&table); table.set_count = FT_TT_REPLICA_MAX_SETS + 1u; ft_tt_test_check("more sets than the format holds is refused", ft_tt_op_replica_table_encode(&table, words) != 0, "set_count 5"); } /** * Every set the format holds, all at the widest destination count, so the record stride is * exercised at its extreme rather than at one. */ static void check_full_table(void) { uint32_t words[FT_TT_REPLICA_TABLE_WORDS]; uint32_t out_x[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_y[FT_TT_REPLICA_MAX_DESTINATIONS]; uint32_t out_base[FT_TT_REPLICA_MAX_DESTINATIONS]; ft_tt_op_replica_table table; uint32_t set, index; bool all = true; ft_tt_test_step("a FULL table — every set, every destination"); memset(&table, 0, sizeof(table)); table.set_count = FT_TT_REPLICA_MAX_SETS; for (set = 0u; set < FT_TT_REPLICA_MAX_SETS; set++) { table.set[set].count = FT_TT_REPLICA_MAX_DESTINATIONS; for (index = 0u; index < FT_TT_REPLICA_MAX_DESTINATIONS; index++) { table.set[set].destination[index].x = (uint16_t)(1u + index); table.set[set].destination[index].y = (uint16_t)(2u + index); table.set[set].destination[index].base = 0x10000u * (set + 1u) + 0x100u * index; } } ft_tt_test_check("it encodes", ft_tt_op_replica_table_encode(&table, words) == 0, "4 sets x 8"); for (set = 0u; set < FT_TT_REPLICA_MAX_SETS; set++) { uint32_t found = ftr_resolve_destinations(words, table.set[set].destination[0].x, table.set[set].destination[0].y, table.set[set].destination[0].base, out_x, out_y, out_base); all = all && found == FT_TT_REPLICA_MAX_DESTINATIONS; for (index = 0u; index < FT_TT_REPLICA_MAX_DESTINATIONS && found == 8u; index++) all = all && out_base[index] == table.set[set].destination[index].base; } ft_tt_test_check("every set resolves to its own eight", all, "4 of 4"); ft_tt_test_check_fmt("the last set's record ends inside the reservation", (ftr_record_offset(FT_TT_REPLICA_MAX_SETS - 1u) + FT_TT_REPLICA_RECORD_WORDS) * 4u <= FT_TT_REPLICA_TABLE_BYTES, "%u B of %u", (unsigned)(FT_TT_REPLICA_TABLE_WORDS * 4u), (unsigned)FT_TT_REPLICA_TABLE_BYTES); } int main(void) { printf("=== the resident replica table, off hardware\n"); check_encode_round_trip(); check_miss_is_the_shipped_path(); check_empty_and_absent(); check_refusals(); check_full_table(); return ft_tt_test_finish(); }