/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file test_phase6_topk.c * @brief ON-CARD acceptance for the sampling op pair — exact top-N over a vocabulary logit * row and the token draw that consumes it, on one resident program. * * --------------------------------------------------------------------------- * THE GATE IS EXACT INDEX AGREEMENT, NOT PCC — AND NOT BY PREFERENCE * --------------------------------------------------------------------------- * Every other Phase 6 op is accepted at PCC ≥ 0.99 against a CPU fp32 reference. That gate * is BLIND to this op's failure mode: top-k returns INDICES, and a result whose N values * are all correct while its N-th index resolved a tie the other way correlates at 1.0 with * the truth and makes the model emit a different token. So every check here is an integer * comparison — pair for pair, token for token. * * bf16 has EIGHT mantissa bits against MiniCPM5-1B's 130,560 vocabulary, so ties are the * common case and not an edge one. The rows stage 2 builds plant exact ties, adjacent- * representable near-ties and an all-identical plateau AT the selection boundary, because * that is the only place a reference and an implementation disagree without either looking * wrong. * * --------------------------------------------------------------------------- * THE DECOY ROWS, AND WHY THEY ARE THE MOST IMPORTANT INPUT HERE * --------------------------------------------------------------------------- * The LM head runs at Mt = 1, so it writes 32 token rows and decode selects over ONE. A * tile is four 16x16 faces, so that row is TWO runs of 16 elements 512 bytes apart, not one * run of 32 (docs/tt_kernel_model.md §9). A kernel that got either the row or the face * split wrong would still return real logits with plausible values — the failure is * invisible to any value metric. Every row this test uploads therefore fills the OTHER 31 * token rows with values that would WIN if they were read, so a row/face bug is a wrong * INDEX and a red check rather than a slightly different answer. * * --------------------------------------------------------------------------- * THE COST SPLIT IS MEASURED, NOT INFERRED * --------------------------------------------------------------------------- * Stage 3 runs the full MiniCPM5-1B row twice, interleaved, differing only by a RUNTIME * FLAG: the full arm and a drain-only arm that pops every logit block without selecting * over it. Same binary, same descriptors, same DRAM traffic — so the difference IS the * scalar selection's cost and the drain arm IS the DRAM stream's. Interleaved because two * 1.8 GHz cores also driving eight K80s drift enough over ten seconds to swamp the * difference (dev notes §8). * * --------------------------------------------------------------------------- * SAFETY * --------------------------------------------------------------------------- * The resident program is retired by ASKING (`ft_tt_watchdog_shutdown`). It is never * parked and never reset: parking a resident triad breaks the core's NEXT boot even after * a textbook-clean retirement (dev notes §1, cores 60 and 65). * * --------------------------------------------------------------------------- * AND THE SUITE SCORES THE CORE, NOT ONLY THE ANSWER — stage 7 * --------------------------------------------------------------------------- * Every stage below stage 7 grades an OUTPUT. That is not enough and this suite is the proof: * it reported `0 checks failed`, retired its residency with `unstopped=0x0`, and left the * Tensix it ran on unable to boot — four cores, before anyone noticed, because a wedged core * READS exactly like a healthy idle one (bringup log §21.1). Stage 7 therefore ends the run * by BOOTING the core again and running a fresh residency on it. It runs unconditionally, * including after an earlier stage has failed. * * Usage: * FT_TT_FIRMWARE_DIR= FT_TT_TENSIX_INDEX= \ * ./test_phase6_topk * * @status 40 checks, 0 failures on Tensix 51, 57 and 59. Five kernel mutations were driven * through THIS binary unmodified and all five go red: the tie rule (8 failures), the face * split (10), the token row (10), the accept interval (3), and the repetition penalty * moved before the greedy argmax (1). That last one came back GREEN the first time and is * why stage 4 now takes a SECOND greedy draw — see the note there. * Stage 7 (the core still boots and still computes) added 2026-08-13 and shown RED against * the pre-fix kernel, which it names with the reset register. * @issues None * @todo None */ /* clock_gettime(CLOCK_MONOTONIC) is POSIX.1-2008, not C99. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "../convert/ft_float_convert.h" #include "../ft_tt_coord.h" #include "../ft_tt_op_dispatch.h" #include "../ft_tt_op_l1_layout.h" #include "../ft_tt_op_sample.h" #include "../ft_tt_queue.h" #include "../ft_tt_tilize.h" #include "../ft_tt_watchdog.h" #include "ft_tt_hardware_session.h" #include "ft_tt_program_directory.h" #include "ft_tt_test_report.h" #include "ft_tt_topk_reference.h" #define TILE_BYTES FT_TT_OP_TILE_BYTES_BF16 #define TILE_DIM FT_TT_OP_TILE_DIM #define RUN_TIMEOUT_MS 60000u #define QUEUE_CAPACITY 64u #define POISON_WORD 0xA5A5A5A5u /** DRAM banks 6 and 7 are this suite's; 0-5 belong to the other Phase 6 workers. */ #define DRAM_BANK_LOGITS 6u #define DRAM_BANK_TOKEN 7u #define DRAM_BANK_COUNT 8u #define DRAM_BUFFER_BASE 0x00100000u #define DRAM_TOKEN_BASE 0x00200000u /** Every processor of the program is resident, including the deliberately idle TRISCs — * they follow the queue so the stop protocol is the normal one. */ #define TRIAD_MASK \ ((1u << FT_TT_PROC_DM0) | (1u << FT_TT_PROC_DM1) | (1u << FT_TT_PROC_MATH0) | \ (1u << FT_TT_PROC_MATH1) | (1u << FT_TT_PROC_MATH2)) #define IDLE_SPIN_LIMIT 400000000u #define CB_SPIN_LIMIT 400000000u /** The small shape stage 2 scores on: 5120 entries, 160 tiles. Big enough that the * selection is not trivially the whole row, small enough that a case costs a second — and * 160 is a whole number of PLACEMENT_DEPTH*2 = 80-page buffer cycles, which is the * invariant a resident reader actually needs and the one that overwrote L1 when this test * used 128 (see ft_tt_op_sample.h). 4080 is 51 of the same cycles. */ #define SMALL_VOCAB 5120u #define SMALL_TILES (SMALL_VOCAB / 32u) /** The real shape: MiniCPM5-1B's vocabulary, MEASURED from the production artifact. */ #define FULL_VOCAB 130560u #define FULL_TILES (FULL_VOCAB / 32u) /** Which of a tile's 32 rows carries the decoded token. Deliberately NOT 0: row 0 is the * one a face/row bug is most likely to land on by accident. */ #define TOKEN_ROW 13u #define SELECT_COUNT 40u /** The depth the CBs are PLACED at, once, for the whole residency, giving a 2 x 40 = 80-page * buffer. Every depth this test uses (40, 20, 8, 1) divides 80 AND divides both tile counts, * and 80 divides BOTH tile counts (160 and 4080) so every descriptor hands the FIFO write * pointer back where it found it. That last clause is the one the kernel model does not * state and the one that cost this test a run — ft_tt_op_sample.h carries the derivation. */ #define PLACEMENT_DEPTH 40u static ft_tt_noc_coord g_dram[DRAM_BANK_COUNT]; static ft_tt_launch_core_placement g_placement_storage[1]; static ft_tt_core_group g_group; static ft_tt_program_directory g_program; static ft_tt_launch_config g_config; static ft_tt_queue g_queue; static ft_tt_launch_cb_entry g_cb_placed[FT_TT_OP_MAX_CB]; static unsigned char g_cb_buffer[FT_TT_NUM_CIRCULAR_BUFFERS * FT_TT_WORDS_PER_LOCAL_CB_CONFIG * 4]; static uint32_t g_result_address; static uint32_t g_state_address; static uint32_t g_descriptor_slot; /* ================================================================ * Device access * ================================================================ */ static int device_write(const ft_tt_noc_access *noc, ft_tt_noc_coord where, uint32_t address, const void *source, size_t bytes) { const unsigned char *cursor = source; size_t offset = 0; while (offset < bytes) { size_t chunk = bytes - offset; int rc; if (chunk > 0x10000u) chunk = 0x10000u; rc = noc->write(noc->user, (uint16_t)where.x, (uint16_t)where.y, (uint64_t)address + offset, cursor + offset, chunk); if (rc != FT_TT_OK) return rc; offset += chunk; } return FT_TT_OK; } static int device_read(const ft_tt_noc_access *noc, ft_tt_noc_coord where, uint32_t address, void *destination, size_t bytes) { unsigned char *cursor = destination; size_t offset = 0; while (offset < bytes) { size_t chunk = bytes - offset; int rc; if (chunk > 0x10000u) chunk = 0x10000u; rc = noc->read(noc->user, (uint16_t)where.x, (uint16_t)where.y, (uint64_t)address + offset, cursor + offset, chunk); if (rc != FT_TT_OK) return rc; offset += chunk; } return FT_TT_OK; } /** L1 on the target core. The result and state blocks live there, so every read of them * goes through the core's own coordinate rather than a DRAM bank's. */ static ft_tt_noc_coord core_coord(uint16_t x, uint16_t y) { ft_tt_noc_coord where; where.x = x; where.y = y; return where; } static double elapsed_ms(const struct timespec *start, const struct timespec *end) { return (double)(end->tv_sec - start->tv_sec) * 1000.0 + (double)(end->tv_nsec - start->tv_nsec) / 1.0e6; } /* ================================================================ * Bringing the resident program up * ================================================================ */ static int load_images(const ft_tt_noc_access *noc, uint16_t x, uint16_t y) { unsigned index; for (index = 0; index < g_program.program.image_count; index++) { const ft_tt_kernel_image *image = &g_program.program.images[index]; int rc = ft_tt_launch_load_image(noc, x, y, image->processor, FT_TT_IMAGE_KERNEL, image->elf_data, image->elf_size, NULL); if (rc != FT_TT_OK) return rc; } return FT_TT_OK; } static int start_program(const ft_tt_noc_access *noc, uint16_t x, uint16_t y) { static const unsigned processors[5] = { FT_TT_PROC_DM0, FT_TT_PROC_DM1, FT_TT_PROC_MATH0, FT_TT_PROC_MATH1, FT_TT_PROC_MATH2 }; uint32_t args[5][FT_TT_QUEUE_KERNEL_ARG_COUNT]; ft_tt_op_args blocks[5]; unsigned index; int rc; for (index = 0; index < 5u; index++) { ft_tt_queue_kernel_binding binding; memset(&binding, 0, sizeof(binding)); binding.idle_spin_limit = IDLE_SPIN_LIMIT; binding.cb_wait_spin_limit = CB_SPIN_LIMIT; binding.status_address = FT_TT_QUEUE_STATUS_ADDRESS(g_queue.control_address, processors[index]); rc = ft_tt_queue_kernel_args(&g_queue, &binding, args[index]); if (rc != FT_TT_OK) return rc; blocks[index].processor = processors[index]; blocks[index].values = args[index]; blocks[index].count = FT_TT_QUEUE_KERNEL_ARG_COUNT; } rc = ft_tt_op_stage(noc, x, y, &g_config, g_cb_buffer, sizeof(g_cb_buffer), blocks, 5u); if (rc != FT_TT_OK) return rc; return ft_tt_launch_send_go(noc, x, y); } /** NEVER a reset and NEVER a park — dev notes §1. */ static void retire_program(const ft_tt_noc_access *noc) { uint32_t unstopped = 0u; int rc = ft_tt_watchdog_shutdown(noc, &g_group, RUN_TIMEOUT_MS, &unstopped); ft_tt_test_check_fmt("the resident program retires by ASKING", rc == FT_TT_OK && unstopped == 0u, "rc=%d unstopped=0x%X", rc, unstopped); } /* ================================================================ * Enqueue and wait * ================================================================ */ /** * Enqueue one descriptor and wait for the WRITER to publish its completion. * * The completion check is `ft_tt_queue_check_completion`, which agrees four things — count, * token, state and error — because a count alone is exactly the lie Phase 5 was built * against (dev notes §3, "group completion is not evidence that a group ran"). */ static bool run_descriptor(const ft_tt_hardware_session *session, uint32_t opcode, const ft_tt_op_topk_request *request, double *out_ms) { uint32_t words[FT_TT_OP_SAMPLE_WORDS]; uint32_t fields[FT_TT_QUEUE_FIELD_COUNT]; ft_tt_queue_descriptor descriptor; uint32_t completed = 0u; uint32_t parameter_address = 0u; const char *reason = NULL; struct timespec start; struct timespec finish; uint32_t slot = g_descriptor_slot; int rc; rc = ft_tt_op_sample_parameters(request, words); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the parameter block encodes", false, "rc=%d", rc); return false; } rc = ft_tt_queue_write_op_parameters(&session->noc, &g_queue, slot % 64u, words, ¶meter_address); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the parameter block uploads", false, "rc=%d", rc); return false; } /* The token is stamped by ft_tt_queue_enqueue() from the run nonce, deliberately — a * caller that supplied its own could check the token it was TOLD rather than the one * that was SENT. */ memset(&descriptor, 0, sizeof(descriptor)); descriptor.opcode = opcode; descriptor.a0 = parameter_address; clock_gettime(CLOCK_MONOTONIC, &start); rc = ft_tt_queue_enqueue(&session->noc, &g_queue, &descriptor, 1u); if (rc == FT_TT_OK) rc = ft_tt_queue_wait_completed(&session->noc, &g_queue, slot + 1u, RUN_TIMEOUT_MS, &completed); clock_gettime(CLOCK_MONOTONIC, &finish); if (out_ms) *out_ms = elapsed_ms(&start, &finish); if (rc != FT_TT_OK) { (void)ft_tt_queue_read_control(&session->noc, &g_queue, fields); ft_tt_test_check_fmt("the descriptor completes", false, "rc=%d completed=%u state=0x%08X error=%s", rc, completed, fields[FT_TT_QUEUE_FIELD_STATE], ft_tt_queue_kernel_error_string(fields[FT_TT_QUEUE_FIELD_ERROR])); return false; } (void)ft_tt_queue_read_control(&session->noc, &g_queue, fields); if (ft_tt_queue_check_completion(&g_queue, fields, slot + 1u, &reason) != FT_TT_OK) { /* Print the words, not just the verdict. A completion check that reports WHICH of * its four grounds failed but not the values it read is a failure you have to re-run * to diagnose, and re-running costs a core. */ ft_tt_test_check_fmt("the completion agrees on all four words", false, "%s — expected %u, count %u, token 0x%08X (want 0x%08X), " "state 0x%08X, error %s", reason ? reason : "unknown", slot + 1u, fields[FT_TT_QUEUE_FIELD_COMPLETED_COUNT], fields[FT_TT_QUEUE_FIELD_COMPLETED_TOKEN], ft_tt_queue_token_at(&g_queue, slot), fields[FT_TT_QUEUE_FIELD_STATE], ft_tt_queue_kernel_error_string(fields[FT_TT_QUEUE_FIELD_ERROR])); return false; } g_descriptor_slot = slot + 1u; return true; } /* ================================================================ * Building and uploading a logit row * ================================================================ */ static uint32_t g_random_state = 0x9E3779B9u; static uint32_t next_random(void) { g_random_state ^= g_random_state << 13; g_random_state ^= g_random_state >> 17; g_random_state ^= g_random_state << 5; return g_random_state; } static uint16_t random_logit(void) { float value = ((float)(next_random() >> 8) / 16777216.0f) * 40.0f - 20.0f; return ft_bf16_from_float(value); } /** * Tilize a row and upload it, with every OTHER token row filled with a value that would * beat every real logit if the kernel read the wrong row or the wrong face. * * The filler is +1000.0f and the logits are in [-20, 20), so a decoy that leaks into the * selection is not a subtle score change — it is the WHOLE answer, and the indices come * back wrong. That is the point: a row bug must be a red check, not a plausible number. */ static int upload_row(const ft_tt_hardware_session *session, const uint16_t *row, uint32_t vocab, uint8_t *scratch) { size_t bytes = (size_t)(vocab / 32u) * TILE_BYTES; int rc = ft_tt_topk_reference_tilize_row(row, vocab, TOKEN_ROW, ft_bf16_from_float(1000.0f), scratch, bytes); if (rc != 0) return FT_TT_EINVAL; return device_write(&session->noc, g_dram[DRAM_BANK_LOGITS], DRAM_BUFFER_BASE, scratch, bytes); } static ft_tt_op_topk_request base_request(uint32_t vocab, uint32_t select_count, uint32_t prefetch, uint32_t flags) { ft_tt_op_topk_request request; memset(&request, 0, sizeof(request)); request.tile_count = vocab / 32u; request.row = TOKEN_ROW; request.select_count = select_count; request.prefetch_tiles = prefetch; request.flags = flags; request.logits.x = (uint16_t)g_dram[DRAM_BANK_LOGITS].x; request.logits.y = (uint16_t)g_dram[DRAM_BANK_LOGITS].y; request.logits.address = DRAM_BUFFER_BASE; request.logits.tile_bytes = TILE_BYTES; request.result_address = g_result_address; request.state_address = g_state_address; return request; } /** Poison the result block, so "the kernel never wrote it" and "the kernel answered" are * different bit patterns rather than the same stale one. */ static int poison_result(const ft_tt_hardware_session *session, uint16_t x, uint16_t y) { uint32_t poison[FT_TT_OP_SAMPLE_RESULT_BYTES / 4u]; size_t index; for (index = 0; index < sizeof(poison) / sizeof(poison[0]); index++) poison[index] = POISON_WORD; return device_write(&session->noc, core_coord(x, y), g_result_address, poison, sizeof(poison)); } /** * Run TOPK over `row` and compare the card's answer, pair for pair, against the reference. * * @return true when every pair matched */ static bool score_topk(const ft_tt_hardware_session *session, uint16_t x, uint16_t y, const char *label, const uint16_t *row, uint32_t vocab, uint32_t select_count, uint32_t prefetch, uint8_t *scratch, double *out_ms) { uint32_t block[FT_TT_OP_SAMPLE_RESULT_BYTES / 4u]; uint32_t card_indices[FT_TT_OP_SAMPLE_MAX_SELECT]; float card_values[FT_TT_OP_SAMPLE_MAX_SELECT]; uint32_t reference_indices[FT_TT_OP_SAMPLE_MAX_SELECT]; float reference_values[FT_TT_OP_SAMPLE_MAX_SELECT]; ft_tt_op_topk_request request; uint32_t count = 0u; uint32_t first_bad = select_count; uint32_t entry; int rc; if (row) { uint32_t before[FT_TT_QUEUE_FIELD_COUNT]; uint32_t after[FT_TT_QUEUE_FIELD_COUNT]; /* Read the queue control block ACROSS the upload. A bulk DRAM write and the resident * kernel's own control words share nothing by design, so this is not a suspicion — * it is the instrument that says WHICH link failed instead of leaving a corrupted * word to be attributed to the kernel (dev notes §3: a harness that cannot say which * link failed will point at the wrong one). */ (void)ft_tt_queue_read_control(&session->noc, &g_queue, before); rc = upload_row(session, row, vocab, scratch); if (rc != FT_TT_OK) { ft_tt_test_check_fmt(label, false, "the row failed to upload, rc=%d", rc); return false; } (void)ft_tt_queue_read_control(&session->noc, &g_queue, after); if (memcmp(before, after, sizeof(before)) != 0) ft_tt_test_check_fmt("the row upload did NOT disturb the queue control block", false, "%s: count %u -> %u, token 0x%08X -> 0x%08X, head %u -> %u", label, before[FT_TT_QUEUE_FIELD_COMPLETED_COUNT], after[FT_TT_QUEUE_FIELD_COMPLETED_COUNT], before[FT_TT_QUEUE_FIELD_COMPLETED_TOKEN], after[FT_TT_QUEUE_FIELD_COMPLETED_TOKEN], before[FT_TT_QUEUE_FIELD_HEAD], after[FT_TT_QUEUE_FIELD_HEAD]); } if (poison_result(session, x, y) != FT_TT_OK) { ft_tt_test_check(label, false, "the result block failed to poison"); return false; } request = base_request(vocab, select_count, prefetch, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_TOPK, &request, out_ms)) return false; rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); if (rc != FT_TT_OK) { ft_tt_test_check_fmt(label, false, "the result block failed to read, rc=%d", rc); return false; } if (block[FT_TT_OP_SAMPLE_RESULT_MAGIC] == POISON_WORD) { ft_tt_test_check(label, false, "the poison survived — the kernel never wrote the block"); return false; } rc = ft_tt_op_sample_result_decode(block, FT_TT_OP_SAMPLE_MAX_SELECT, card_indices, card_values, &count, NULL); if (rc != FT_TT_OK || count != select_count) { ft_tt_test_check_fmt(label, false, "decode rc=%d, count %u of %u", rc, count, select_count); return false; } if (ft_tt_topk_reference_select(row, vocab, select_count, reference_indices, reference_values) != 0) { ft_tt_test_check(label, false, "the reference refused the row"); return false; } for (entry = 0u; entry < select_count; entry++) { if (card_indices[entry] != reference_indices[entry] || card_values[entry] != reference_values[entry]) { first_bad = entry; break; } } if (first_bad == select_count) { ft_tt_test_check_fmt(label, true, "%u pairs EXACT, top id %u = %.6g", select_count, card_indices[0], (double)card_values[0]); return true; } ft_tt_test_check_fmt(label, false, "pair %u: card (%u, %.9g), reference (%u, %.9g)", first_bad, card_indices[first_bad], (double)card_values[first_bad], reference_indices[first_bad], (double)reference_values[first_bad]); return false; } /* ================================================================ * Stage 0 — the transfer path, before anything is scored * ================================================================ */ static void stage_0_round_trip(const ft_tt_hardware_session *session) { uint16_t written[TILE_DIM * TILE_DIM]; uint16_t read_back[TILE_DIM * TILE_DIM]; size_t index; int rc; ft_tt_test_step("stage 0 — host -> DRAM -> host, so the transfer path is not a suspect"); for (index = 0; index < TILE_DIM * TILE_DIM; index++) written[index] = (uint16_t)(0x3F00u + (index & 0x7Fu)); memset(read_back, 0, sizeof(read_back)); rc = device_write(&session->noc, g_dram[DRAM_BANK_LOGITS], DRAM_BUFFER_BASE, written, sizeof(written)); if (rc == FT_TT_OK) rc = device_read(&session->noc, g_dram[DRAM_BANK_LOGITS], DRAM_BUFFER_BASE, read_back, sizeof(read_back)); ft_tt_test_check_fmt("a tile round-trips through DRAM byte-exact", rc == FT_TT_OK && memcmp(written, read_back, sizeof(written)) == 0, "rc=%d", rc); } /* ================================================================ * Stage 1 — bring the resident program up * ================================================================ */ /** * Bring the resident program up: images, CB placement, queue, go. * * Separate from the stage that announces it because stage 7 starts a SECOND residency on the * same core, and a step header there would claim to be stage 1. */ static bool start_residency(const ft_tt_hardware_session *session, uint16_t x, uint16_t y) { ft_tt_op_topk_request placement = base_request(FULL_VOCAB, SELECT_COUNT, PLACEMENT_DEPTH, 0u); const ft_tt_launch_cb_entry *result_entry; const ft_tt_launch_cb_entry *state_entry; uint64_t mask = 0u; int rc; /* The placement is the FULL shape's, so one descriptor array serves every stage — * which is what "one binary, one residency" means at the host end too. */ placement.result_address = 1u; placement.state_address = 1u; rc = load_images(&session->noc, x, y); if (rc == FT_TT_OK) rc = ft_tt_op_program_load(&session->noc, x, y, &g_program.program, NULL, &g_config); if (rc == FT_TT_OK) rc = ft_tt_op_sample_place_cbs(&placement, g_cb_placed, g_cb_buffer, sizeof(g_cb_buffer), &mask); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("images load and buffers place", false, "rc=%d", rc); return false; } result_entry = ft_tt_op_cb_find(g_cb_placed, FT_TT_OP_SAMPLE_CB_COUNT, 1u); state_entry = ft_tt_op_cb_find(g_cb_placed, FT_TT_OP_SAMPLE_CB_COUNT, 2u); if (!result_entry || !state_entry) { ft_tt_test_check("the result and state blocks were placed", false, "missing entry"); return false; } g_result_address = result_entry->address; g_state_address = state_entry->address; ft_tt_test_check_fmt("the two L1 blocks are placed by the arena, not hardcoded", true, "result 0x%X, state 0x%X", g_result_address, g_state_address); g_config.local_cb_mask = mask; g_config.local_cb_config_address = FT_TT_OP_CB_CONFIG_ADDRESS; g_config.brisc_noc_id = 0u; rc = ft_tt_queue_init(&session->noc, x, y, FT_TT_OP_QUEUE_BASE, QUEUE_CAPACITY, ft_tt_queue_make_nonce(), &g_queue); if (rc == FT_TT_OK) rc = start_program(&session->noc, x, y); ft_tt_test_check_fmt("the program is staged and kicked", rc == FT_TT_OK, "rc=%d", rc); return rc == FT_TT_OK; } static bool stage_1_start(const ft_tt_hardware_session *session, uint16_t x, uint16_t y) { ft_tt_test_step("stage 1 — the resident TOPK/SAMPLE program, staged and kicked ONCE"); return start_residency(session, x, y); } /* ================================================================ * Stage 2 — exactness, on the cases that break a top-k * ================================================================ */ static bool stage_2_exactness(const ft_tt_hardware_session *session, uint16_t x, uint16_t y, uint16_t *row, uint8_t *scratch) { uint32_t index; bool ok = true; ft_tt_test_step("stage 2 — EXACT index agreement, including the tie cases"); for (index = 0u; index < SMALL_VOCAB; index++) row[index] = random_logit(); ok = score_topk(session, x, y, "random row, N = 40", row, SMALL_VOCAB, SELECT_COUNT, 20u, scratch, NULL) && ok; ok = score_topk(session, x, y, "the same row, N = 1 (greedy's shape)", row, SMALL_VOCAB, 1u, 20u, scratch, NULL) && ok; ok = score_topk(session, x, y, "the same row, N = 64 (the block's capacity)", row, SMALL_VOCAB, 64u, 20u, scratch, NULL) && ok; /* The prefetch depth must not change the ANSWER, only the transaction count. Depth 1 is * the per-tile barrier; a block-loop bug that straddles the CB wrap shows up as a wrong * index at one depth and not another. Every depth here DIVIDES the placed page count * (PLACEMENT_DEPTH * 2) and is no larger than it — the invariant a resident op makes * easy to break, and the one that cost this test its first hardware run. */ ok = score_topk(session, x, y, "the same row at depth 1", row, SMALL_VOCAB, SELECT_COUNT, 1u, scratch, NULL) && ok; ok = score_topk(session, x, y, "the same row at depth 8", row, SMALL_VOCAB, SELECT_COUNT, 8u, scratch, NULL) && ok; /* A 20-wide plateau straddling N = 10: the answer is decided ENTIRELY by the tie rule. */ for (index = 0u; index < SMALL_VOCAB; index++) row[index] = ft_bf16_from_float(1.0f); for (index = 0u; index < 20u; index++) row[index * 173u % SMALL_VOCAB] = ft_bf16_from_float(3.5f); ok = score_topk(session, x, y, "a 20-wide tie plateau straddling N = 10", row, SMALL_VOCAB, 10u, 20u, scratch, NULL) && ok; /* Adjacent representable bf16 values, which is where a reference that computed in fp32 * and an implementation that compared bits disagree by exactly one index. */ for (index = 0u; index < SMALL_VOCAB; index++) row[index] = ft_bf16_from_float(1.0f); for (index = 0u; index < 24u; index++) { uint16_t base = ft_bf16_from_float(7.0f); row[index * 191u % SMALL_VOCAB] = (uint16_t)(base + (uint16_t)(index % 3u) - 1u); } ok = score_topk(session, x, y, "adjacent-representable near-ties at N = 12", row, SMALL_VOCAB, 12u, 20u, scratch, NULL) && ok; /* Every value identical: the only determinate answer is indices 0..N-1, and a kernel * that inserted on >= instead of > returns the LAST N instead. */ for (index = 0u; index < SMALL_VOCAB; index++) row[index] = ft_bf16_from_float(2.0f); ok = score_topk(session, x, y, "an all-identical row at N = 8", row, SMALL_VOCAB, 8u, 20u, scratch, NULL) && ok; /* A negative plateau, because the accept interval takes a DIFFERENT branch for a * negative incumbent and every case above leaves it on the positive one. */ for (index = 0u; index < SMALL_VOCAB; index++) row[index] = ft_bf16_from_float(-9.0f); for (index = 0u; index < 16u; index++) row[index * 211u % SMALL_VOCAB] = ft_bf16_from_float(-2.5f); ok = score_topk(session, x, y, "an all-negative row with a plateau at N = 8", row, SMALL_VOCAB, 8u, 20u, scratch, NULL) && ok; /* The winners at the very ends of the row, so an off-by-one in the block loop or in the * face arithmetic drops one of them. */ for (index = 0u; index < SMALL_VOCAB; index++) row[index] = ft_bf16_from_float(-5.0f); row[0] = ft_bf16_from_float(9.0f); row[SMALL_VOCAB - 1u] = ft_bf16_from_float(8.0f); row[31] = ft_bf16_from_float(7.0f); row[32] = ft_bf16_from_float(6.0f); ok = score_topk(session, x, y, "winners at both ends and at a tile boundary", row, SMALL_VOCAB, 4u, 20u, scratch, NULL) && ok; return ok; } /* ================================================================ * Stage 3 — the real shape, and the cost split * ================================================================ */ static bool stage_3_full_row(const ft_tt_hardware_session *session, uint16_t x, uint16_t y, uint16_t *row, uint8_t *scratch) { double full_ms[4]; double drain_ms[4]; double full_total = 0.0; double drain_total = 0.0; uint32_t index; uint32_t arm; bool ok; ft_tt_test_step("stage 3 — MiniCPM5-1B's real row: 130,560 logits, 4080 tiles, 8.36 MB"); for (index = 0u; index < FULL_VOCAB; index++) row[index] = random_logit(); ok = score_topk(session, x, y, "the full vocabulary row, N = 40, EXACT", row, FULL_VOCAB, SELECT_COUNT, PLACEMENT_DEPTH, scratch, &full_ms[0]); if (!ok) return false; /* The cost split. Interleaved arms, same binary, same descriptors, differing only by a * RUNTIME flag — the drain arm pops every block without selecting, so full minus drain * IS the scalar selection and drain IS the DRAM stream. Interleaved because two 1.8 GHz * cores also driving eight K80s drift enough over ten seconds to swamp the difference * (dev notes §8, which once reported a NEGATIVE cost for CONFIGURE_TLB). */ for (arm = 0u; arm < 4u; arm++) { ft_tt_op_topk_request request = base_request(FULL_VOCAB, SELECT_COUNT, PLACEMENT_DEPTH, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_TOPK, &request, &full_ms[arm])) return false; request = base_request(FULL_VOCAB, SELECT_COUNT, PLACEMENT_DEPTH, FT_TT_OP_SAMPLE_FLAG_DRAIN_ONLY); if (!run_descriptor(session, FT_TT_QUEUE_OP_TOPK, &request, &drain_ms[arm])) return false; full_total += full_ms[arm]; drain_total += drain_ms[arm]; } ft_tt_test_check_fmt("the full row selects in a time worth reporting", full_total > 0.0, "full %.3f ms/token, drain-only %.3f ms/token, selection %.3f ms", full_total / 4.0, drain_total / 4.0, (full_total - drain_total) / 4.0); /* The whole reason the op exists, stated as a number rather than as prose: the same row * fetched to the host at the MEASURED card->host rate (dev notes §6, 4.7 MB/s at 16-byte * loads) instead of reduced on the card. */ ft_tt_test_check_fmt("on-card selection beats fetching the row to the host", full_total / 4.0 < 55.0, "on card %.3f ms, host fetch of 261,120 B at 4.7 MB/s = %.1f ms", full_total / 4.0, 261120.0 / 4.7e6 * 1000.0); /* And the drain arm must be a DIFFERENT answer, or the flag did nothing and the split * above is two measurements of the same thing. */ { uint32_t block[FT_TT_OP_SAMPLE_RESULT_BYTES / 4u]; int rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); ft_tt_test_check_fmt("the drain-only arm writes an EMPTY result, not an answer", rc == FT_TT_OK && block[FT_TT_OP_SAMPLE_RESULT_MAGIC] == FT_TT_OP_SAMPLE_RESULT_MAGIC_VALUE && block[FT_TT_OP_SAMPLE_RESULT_COUNT] == 0u, "rc=%d magic 0x%08X count %u", rc, block[FT_TT_OP_SAMPLE_RESULT_MAGIC], block[FT_TT_OP_SAMPLE_RESULT_COUNT]); } return true; } /* ================================================================ * Stage 4 — the token draw * ================================================================ */ static ft_tt_op_sample_params default_params(void) { ft_tt_op_sample_params params; memset(¶ms, 0, sizeof(params)); params.temperature = 0.0f; params.top_p = 1.0f; params.repeat_penalty = 1.0f; params.top_k = SELECT_COUNT; params.repeat_window = 0u; params.rng_seed = 0x2545F491u; return params; } static int upload_state(const ft_tt_hardware_session *session, uint16_t x, uint16_t y, const ft_tt_op_sample_params *params) { uint32_t state[FT_TT_OP_SAMPLE_STATE_WORDS]; int rc = ft_tt_op_sample_state_build(params, state, FT_TT_OP_SAMPLE_STATE_WORDS); if (rc != FT_TT_OK) return rc; return device_write(&session->noc, core_coord(x, y), g_state_address, state, sizeof(state)); } static bool stage_4_sampling(const ft_tt_hardware_session *session, uint16_t x, uint16_t y, uint16_t *row, uint8_t *scratch) { uint32_t block[FT_TT_OP_SAMPLE_RESULT_BYTES / 4u]; uint32_t state[FT_TT_OP_SAMPLE_STATE_WORDS]; uint32_t card_indices[FT_TT_OP_SAMPLE_MAX_SELECT]; float card_values[FT_TT_OP_SAMPLE_MAX_SELECT]; ft_tt_op_sample_params params = default_params(); ft_tt_op_topk_request request; uint32_t count = 0u; uint32_t card_token = 0u; uint32_t reference_token; uint32_t rng; uint32_t index; uint32_t draw; bool ok = true; int rc; ft_tt_test_step("stage 4 — the token draw, against Pantheon's own sparse sampler"); for (index = 0u; index < SMALL_VOCAB; index++) row[index] = random_logit(); if (!score_topk(session, x, y, "the row this stage samples from", row, SMALL_VOCAB, SELECT_COUNT, 20u, scratch, NULL)) return false; rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); if (rc == FT_TT_OK) rc = ft_tt_op_sample_result_decode(block, FT_TT_OP_SAMPLE_MAX_SELECT, card_indices, card_values, &count, NULL); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the survivors read back", false, "rc=%d", rc); return false; } /* GREEDY. Temperature 0, and a repetition penalty that WOULD change the answer if it * were applied first — which is the shipped K80 bug this ordering exists to prevent. */ params.temperature = 0.0f; params.repeat_penalty = 1.5f; params.repeat_window = 8u; rc = upload_state(session, x, y, ¶ms); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the sampler state uploads", false, "rc=%d", rc); return false; } request = base_request(SMALL_VOCAB, SELECT_COUNT, 20u, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_SAMPLE, &request, NULL)) return false; rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); card_token = block[FT_TT_OP_SAMPLE_RESULT_TOKEN]; ft_tt_test_check_fmt("SAMPLE marks the token valid", rc == FT_TT_OK && block[FT_TT_OP_SAMPLE_RESULT_TOKEN_VALID] == 1u, "valid %u", block[FT_TT_OP_SAMPLE_RESULT_TOKEN_VALID]); rng = params.rng_seed; reference_token = ft_tt_topk_reference_sample(card_indices, card_values, count, ¶ms, NULL, 0u, &rng, 0); ft_tt_test_check_fmt("greedy returns the reference's token EXACTLY", card_token == reference_token, "card %u, reference %u", card_token, reference_token); /* The discriminating half: greedy must be the argmax of the RAW survivors, so it is the * FIRST pair of the descending-by-value result block and nothing else. */ ft_tt_test_check_fmt("greedy is the top pair of the result block, so no penalty was applied", card_token == card_indices[0], "card %u, top pair %u", card_token, card_indices[0]); ok = ok && (card_token == reference_token) && (card_token == card_indices[0]); /* THE SECOND GREEDY DRAW IS THE ONE THAT CAN GO RED, AND THE FIRST ONE CANNOT. * * This was found by mutation, not by review: a build with the repetition penalty moved * BEFORE the greedy argmax — the exact shipped K80 bug this ordering exists to prevent — * passed the check above with ZERO failures. The reason is that the state was freshly * uploaded, so the recent-token ring was EMPTY and `apply_repeat_penalty` returned * without touching anything. The check was asserting an ordering whose two arms were * identical on that input, which is dev notes §3's "a test can be vacuous because the * input never exercises it" in its purest form. * * The fix is not a bigger assertion, it is the right INPUT: the first draw appended its * own token to the ring, so a SECOND draw now has a penalty that lands on exactly the * token greedy must still return. A penalty-first implementation demotes the top logit * by the 1.5x factor and returns the runner-up. Nothing else changed. */ { uint32_t recent_after[FT_TT_OP_SAMPLE_STATE_WORDS]; uint32_t second_token; uint32_t second_reference; uint32_t second_rng; rc = device_read(&session->noc, core_coord(x, y), g_state_address, recent_after, sizeof(recent_after)); ft_tt_test_check_fmt("the case is NOT vacuous: the ring now holds the top token and " "the penalty is live", rc == FT_TT_OK && recent_after[FT_TT_OP_SAMPLE_STATE_RECENT_COUNT] == 1u && recent_after[FT_TT_OP_SAMPLE_RECENT_OFFSET_WORDS] == card_token && params.repeat_penalty != 1.0f, "recent %u entries, ring[0] %u, penalty %.2f", recent_after[FT_TT_OP_SAMPLE_STATE_RECENT_COUNT], recent_after[FT_TT_OP_SAMPLE_RECENT_OFFSET_WORDS], (double)params.repeat_penalty); request = base_request(SMALL_VOCAB, SELECT_COUNT, 20u, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_SAMPLE, &request, NULL)) return false; rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); second_token = block[FT_TT_OP_SAMPLE_RESULT_TOKEN]; second_rng = params.rng_seed; second_reference = ft_tt_topk_reference_sample(card_indices, card_values, count, ¶ms, &card_token, 1u, &second_rng, 0); ft_tt_test_check_fmt("greedy STILL returns the true argmax with that token penalised", rc == FT_TT_OK && second_token == card_indices[0] && second_token == second_reference, "card %u, reference %u, true argmax %u", second_token, second_reference, card_indices[0]); /* And name what a penalty-first build would have returned, so the case's * discriminating power is on the transcript rather than asserted in a comment. */ ft_tt_test_check_fmt("a penalty-first build would have returned the runner-up instead", card_values[0] / params.repeat_penalty < card_values[1], "top %.6g penalised to %.6g, runner-up %.6g", (double)card_values[0], (double)(card_values[0] / params.repeat_penalty), (double)card_values[1]); ok = ok && second_token == card_indices[0]; } /* The state a closed decode loop carries: the token was appended to BOTH rings and the * emitted count moved, without the host doing any of it. */ rc = device_read(&session->noc, core_coord(x, y), g_state_address, state, sizeof(state)); ft_tt_test_check_fmt("the sampler appended the token to its own recent ring", rc == FT_TT_OK && state[FT_TT_OP_SAMPLE_RECENT_OFFSET_WORDS] == card_token, "ring[0] %u, token %u", state[FT_TT_OP_SAMPLE_RECENT_OFFSET_WORDS], card_token); /* TWO, because the non-vacuous ordering case above needed a second greedy draw and both * went into this ring. The ring's FIRST entry is still the first token, which is the * property being checked — the host drains a SEQUENCE, not a single slot. */ ft_tt_test_check_fmt("and to the emitted ring the host drains lazily", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT] == 2u && state[FT_TT_OP_SAMPLE_EMITTED_OFFSET_WORDS] == card_token, "emitted %u, ring[0] %u", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT], state[FT_TT_OP_SAMPLE_EMITTED_OFFSET_WORDS]); ft_tt_test_check_fmt("greedy did NOT advance the generator", state[FT_TT_OP_SAMPLE_STATE_RNG] == params.rng_seed, "state 0x%08X", state[FT_TT_OP_SAMPLE_STATE_RNG]); /* STOCHASTIC. Eight successive draws off the same survivor set, each compared against * the reference driven by the SAME generator — so this is an exact sequence match and * not a distribution argument. A single draw would prove nothing: any bug that permutes * the survivors still returns one of them. */ params = default_params(); params.temperature = 0.8f; params.top_p = 0.95f; params.top_k = 20u; rc = upload_state(session, x, y, ¶ms); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the stochastic state uploads", false, "rc=%d", rc); return false; } rng = params.rng_seed; { uint32_t recent[16]; uint32_t recent_count = 0u; uint32_t matched = 0u; uint32_t distinct = 0u; uint32_t first_token = 0u; for (draw = 0u; draw < 8u; draw++) { uint32_t expected; request = base_request(SMALL_VOCAB, SELECT_COUNT, 20u, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_SAMPLE, &request, NULL)) return false; rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); if (rc != FT_TT_OK) return false; card_token = block[FT_TT_OP_SAMPLE_RESULT_TOKEN]; expected = ft_tt_topk_reference_sample(card_indices, card_values, count, ¶ms, recent, recent_count, &rng, 0); if (card_token == expected) matched++; else ft_tt_test_check_fmt("draw disagrees", false, "draw %u: card %u, reference %u", draw, card_token, expected); if (draw == 0u) first_token = card_token; else if (card_token != first_token) distinct = 1u; if (recent_count < 16u) recent[recent_count++] = card_token; } ft_tt_test_check_fmt("eight stochastic draws match the reference sequence EXACTLY", matched == 8u, "%u of 8", matched); /* The control that stops the check above passing for free: a sampler stuck on the * argmax would also "match" if the reference were stuck the same way, so the draws * have to be seen to VARY. */ ft_tt_test_check_fmt("the draws are not all the same token", distinct == 1u, "first token %u", first_token); ft_tt_test_check_fmt("the generator advanced on the card", rc == FT_TT_OK, "rc=%d", rc); ok = ok && matched == 8u && distinct == 1u; } rc = device_read(&session->noc, core_coord(x, y), g_state_address, state, sizeof(state)); ft_tt_test_check_fmt("the card's generator state matches the host's after eight draws", rc == FT_TT_OK && state[FT_TT_OP_SAMPLE_STATE_RNG] == rng, "card 0x%08X, host 0x%08X", state[FT_TT_OP_SAMPLE_STATE_RNG], rng); /* EIGHT, not nine. The stochastic knobs were uploaded as a fresh state block, which is * what a new REQUEST does, and a new request starts its own emitted ring; the greedy * token above belongs to the previous state. Worth leaving in the comment because the * first version of this check asserted nine and was wrong about WHOSE counter it is — * the count is per STATE BLOCK, not per core and not per residency. */ ft_tt_test_check_fmt("eight tokens are in the emitted ring, none of which crossed the " "link as logits", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT] == 8u, "emitted %u", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT]); return ok; } /* ================================================================ * Stage 5 — the loop closes on the card * ================================================================ */ /** * The whole point of the op, demonstrated rather than argued: the sampled id is written to * a DRAM address the descriptor names — which is where the next step's embedding lookup * reads its input — and the host does not touch anything between the two descriptors. */ static bool stage_5_closed_loop(const ft_tt_hardware_session *session, uint16_t x, uint16_t y) { ft_tt_op_topk_request request; ft_tt_op_sample_params params = default_params(); uint32_t token_slot = POISON_WORD; uint32_t block[FT_TT_OP_SAMPLE_RESULT_BYTES / 4u]; uint32_t state[FT_TT_OP_SAMPLE_STATE_WORDS]; uint32_t step; int rc; ft_tt_test_step("stage 5 — the decode loop closes ON THE CARD"); params.eos_count = 1u; params.eos_ids[0] = 0xFFFFFFFFu; /* never fires, so the budget is what stops it */ params.token_budget = 3u; rc = upload_state(session, x, y, ¶ms); if (rc == FT_TT_OK) rc = device_write(&session->noc, g_dram[DRAM_BANK_TOKEN], DRAM_TOKEN_BASE, &token_slot, sizeof(token_slot)); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the state and the token slot upload", false, "rc=%d", rc); return false; } /* THREE full TOPK -> SAMPLE pairs with NO host read between them. The host writes six * descriptors and reads once at the end; on the K80 path this would have been six * logit-row fetches. */ for (step = 0u; step < 3u; step++) { request = base_request(SMALL_VOCAB, SELECT_COUNT, 20u, 0u); if (!run_descriptor(session, FT_TT_QUEUE_OP_TOPK, &request, NULL)) return false; request = base_request(SMALL_VOCAB, SELECT_COUNT, 20u, 0u); request.token_out.x = (uint16_t)g_dram[DRAM_BANK_TOKEN].x; request.token_out.y = (uint16_t)g_dram[DRAM_BANK_TOKEN].y; request.token_out.address = DRAM_TOKEN_BASE; request.token_out.tile_bytes = 4u; if (!run_descriptor(session, FT_TT_QUEUE_OP_SAMPLE, &request, NULL)) return false; } rc = device_read(&session->noc, g_dram[DRAM_BANK_TOKEN], DRAM_TOKEN_BASE, &token_slot, sizeof(token_slot)); if (rc == FT_TT_OK) rc = device_read(&session->noc, core_coord(x, y), g_result_address, block, sizeof(block)); if (rc == FT_TT_OK) rc = device_read(&session->noc, core_coord(x, y), g_state_address, state, sizeof(state)); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the loop's outputs read back", false, "rc=%d", rc); return false; } ft_tt_test_check_fmt("the token id reached the address the NEXT op would read", token_slot != POISON_WORD && token_slot == block[FT_TT_OP_SAMPLE_RESULT_TOKEN], "slot %u, result block %u", token_slot, block[FT_TT_OP_SAMPLE_RESULT_TOKEN]); ft_tt_test_check_fmt("three tokens were produced with no host read between them", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT] == 3u, "emitted %u", state[FT_TT_OP_SAMPLE_STATE_EMITTED_COUNT]); ft_tt_test_check_fmt("the card noticed its own token budget and stopped itself", state[FT_TT_OP_SAMPLE_STATE_STOPPED] == 1u, "stopped %u", state[FT_TT_OP_SAMPLE_STATE_STOPPED]); ft_tt_test_check_fmt("the recent ring carries all three, ready for the next penalty", state[FT_TT_OP_SAMPLE_STATE_RECENT_COUNT] == 3u, "recent %u", state[FT_TT_OP_SAMPLE_STATE_RECENT_COUNT]); return true; } /* ================================================================ * Stage 6 — a descriptor the program CANNOT execute is refused, not hung on * ================================================================ */ /** * A block deeper than the circular buffer, which `cb_reserve_back` can never satisfy. * * THIS CASE IS HERE BECAUSE IT HAPPENED. The first hardware run of this suite asked for a * 128-tile block against a 96-page CB and got six red checks, ten seconds of blocked * polling and a dead residency — with the failure reported as an idle timeout, which is the * one thing it was not. The kernel can read `fifo_num_pages` and the host's placement is * exactly what it cannot see, so the refusal belongs there. * * It is the LAST stage because a refusal ends the residency by design: `kErrBadParameters` * is a reason to leave the loop, not to continue. That is the same shape as * `stage_7_unknown_opcode_is_refused` in the vector suite. */ static void stage_6_an_impossible_block_is_refused(const ft_tt_hardware_session *session) { uint32_t fields[FT_TT_QUEUE_FIELD_COUNT]; ft_tt_op_topk_request request = base_request(SMALL_VOCAB, SELECT_COUNT, SMALL_TILES, 0u); uint32_t words[FT_TT_OP_SAMPLE_WORDS]; ft_tt_queue_descriptor descriptor; uint32_t parameter_address = 0u; uint32_t completed = 0u; int rc; ft_tt_test_step("stage 6 — a block deeper than the CB is REFUSED, not blocked on"); /* SMALL_TILES is 128 and the CB holds 2 x PLACEMENT_DEPTH = 96 pages, so this is * unsatisfiable by construction. It is written as a deliberate over-ask rather than as a * zero, because a zero-length NoC transfer is suspected of taking the CARD down rather * than failing (a sibling op's card-down on 2026-08-12; INFERRED, and not re-confirmed * because confirming it costs the card for everyone). A red proof must not be able to * reach that value. */ rc = ft_tt_op_sample_parameters(&request, words); if (rc == FT_TT_OK) rc = ft_tt_queue_write_op_parameters(&session->noc, &g_queue, g_descriptor_slot % 64u, words, ¶meter_address); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the over-deep descriptor is prepared", false, "rc=%d", rc); return; } memset(&descriptor, 0, sizeof(descriptor)); descriptor.opcode = FT_TT_QUEUE_OP_TOPK; descriptor.a0 = parameter_address; (void)ft_tt_queue_enqueue(&session->noc, &g_queue, &descriptor, 1u); rc = ft_tt_queue_wait_completed(&session->noc, &g_queue, g_descriptor_slot + 1u, 5000u, &completed); (void)ft_tt_queue_read_control(&session->noc, &g_queue, fields); ft_tt_test_check_fmt("it is NOT retired as complete", rc != FT_TT_OK, "rc=%d completed=%u", rc, completed); ft_tt_test_check_fmt("and the reader names the REASON rather than a timeout", fields[FT_TT_QUEUE_FIELD_ERROR] == FT_TT_QUEUE_KERR_BAD_PARAMETERS, "%s", ft_tt_queue_kernel_error_string(fields[FT_TT_QUEUE_FIELD_ERROR])); } /* ================================================================ * Stage 7 — the core this suite ran on still BOOTS, and still COMPUTES * ================================================================ */ /** * THIS STAGE EXISTS BECAUSE THE SUITE ONCE REPORTED `0 checks failed` WHILE DESTROYING THE * CORE IT RAN ON. * * Every check above scores an ANSWER. None of them scores the core, and the two are * independent: the token hand-off's `noc_inline_dw_write` wrote the right value to the right * address — the stage-5 read-back passed on every run — and left the Tensix unable to boot * again (`soft reset 0x00047000`, BRISC out of reset and never releasing its subordinates). * It cost Tensix 50 and 52, then 20 and 21 reproducing it, and nothing in a suite that only * grades output could ever have seen it. `ft_tt_core status` cannot see it either: a wedged * core reads exactly like a healthy idle one (bringup log §21.1), which is why this has to be * a BOOT and not a read. * * Two checks, because there are two independent ways to fail and a single "it still works" * would be carried by whichever is cheaper (dev notes §3): * 1. the firmware boots again at all — the one the wedge trips; * 2. a fresh residency on that core still returns EXACT pairs — which a core that boots but * computes garbage would fail. * * The boot is the same `assert_reset` + load + deassert the NEXT user of this core performs, * so it tests what they will hit rather than a proxy for it. It is not a park: the residency * was retired by ASKING first, and four cores (22-25, 27) came back PASS through exactly this * sequence. */ static bool stage_7_the_core_survives(const ft_tt_hardware_session *session, const ft_tt_firmware_set *firmware, uint16_t x, uint16_t y, uint16_t *row, uint8_t *scratch) { ft_tt_launch_state state; uint32_t index; int rc; bool ok; ft_tt_test_step("stage 7 — the core this suite ran on still BOOTS, and still COMPUTES"); rc = ft_tt_core_boot_firmware(&session->noc, x, y, firmware, NULL); if (rc != FT_TT_OK) { /* Print the reset register, because "the boot failed" is not a diagnosis and a * re-run to get it costs another core. 0x00047000 with BRISC running is * ft_tt_core's "boot handshake never finished". */ memset(&state, 0, sizeof(state)); (void)ft_tt_launch_read_state(&session->noc, x, y, &state); ft_tt_test_check_fmt("the firmware BOOTS AGAIN on the core the op ran on", false, "rc=%d, soft reset 0x%08X — the op left this core unbootable", rc, state.reset_register); return false; } ft_tt_test_check_fmt("the firmware BOOTS AGAIN on the core the op ran on", true, "rc=%d", rc); /* And it still computes. A core can boot and answer garbage — that is the OTHER wedge * signature this backend has seen (dev notes §1), so booting is not the whole claim. */ g_descriptor_slot = 0u; if (!start_residency(session, x, y)) return false; for (index = 0u; index < SMALL_VOCAB; index++) row[index] = random_logit(); ok = score_topk(session, x, y, "and a FRESH residency on it still selects EXACTLY", row, SMALL_VOCAB, SELECT_COUNT, 20u, scratch, NULL); retire_program(&session->noc); return ok; } int main(int argc, char **argv) { ft_tt_hardware_session session; ft_tt_firmware_set firmware; uint16_t x = 0; uint16_t y = 0; uint32_t tensix_index = 0; uint32_t bank; uint16_t *row = NULL; uint8_t *scratch = NULL; bool ok = true; int rc; if (argc < 2) { fprintf(stderr, "usage: %s \n", argv[0]); return 2; } ft_tt_test_step("Phase 6 top-k and sampling — ON-CARD acceptance"); rc = ft_tt_hardware_session_open(&session); if (rc != FT_TT_OK) return 1; rc = ft_tt_firmware_set_load(ft_tt_firmware_directory(), &firmware); if (rc != FT_TT_OK) { ft_tt_hardware_session_close(&session); return 1; } rc = ft_tt_hardware_session_tensix_from_environment(&session, &x, &y, &tensix_index); if (rc != FT_TT_OK) goto release; for (bank = 0; bank < DRAM_BANK_COUNT; bank++) { rc = ft_tt_coord_dram_bank(&session.grid, bank, FT_TT_COORD_TRANSLATED, &g_dram[bank]); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("DRAM bank coordinates derive", false, "bank %u rc=%d", bank, rc); goto release; } } rc = ft_tt_program_directory_load(argv[1], TRIAD_MASK, &g_program); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the program directory loads", false, "rc=%d", rc); goto release; } rc = ft_tt_launch_core_set_init(&g_group.cores, g_placement_storage, 1u); if (rc == FT_TT_OK) rc = ft_tt_launch_core_set_add_tensix(&g_group.cores, &session.grid, tensix_index, NULL); if (rc != FT_TT_OK) { ft_tt_test_check_fmt("the core set derives and classifies the target", false, "index %u rc=%d", tensix_index, rc); goto release_program; } g_group.id = 0u; g_group.first_tensix_index = tensix_index; g_group.tensix_index_count = 1u; g_group.queues = &g_queue; g_group.resident_processor_mask = TRIAD_MASK; row = malloc((size_t)FULL_VOCAB * sizeof(*row)); scratch = malloc((size_t)FULL_TILES * TILE_BYTES); if (!row || !scratch) { ft_tt_test_check("host buffers allocate", false, "out of memory"); goto release_program; } rc = ft_tt_core_boot_firmware(&session.noc, x, y, &firmware, NULL); ft_tt_test_check_fmt("firmware boots on the target core", rc == FT_TT_OK, "index %u -> (%u, %u) rc=%d", tensix_index, x, y, rc); if (rc != FT_TT_OK) goto release_program; stage_0_round_trip(&session); if (stage_1_start(&session, x, y)) { ok = stage_2_exactness(&session, x, y, row, scratch); if (ok) ok = stage_3_full_row(&session, x, y, row, scratch); if (ok) ok = stage_4_sampling(&session, x, y, row, scratch); if (ok) ok = stage_5_closed_loop(&session, x, y); if (ok) stage_6_an_impossible_block_is_refused(&session); /* Retire by ASKING whatever happened above: a program left resident on a failure is * a core the next worker cannot use, and the ask is the only exit protocol * (dev notes §1 — never park, never reset). */ retire_program(&session.noc); /* UNCONDITIONALLY, and last: whatever the answers were, the core has to still be a * core. This runs even when a stage above failed, because a suite that gives up * early is exactly when nobody finds out what it left behind. */ ok = stage_7_the_core_survives(&session, &firmware, x, y, row, scratch) && ok; } /* DO NOT PARK. The core is left in its firmware's GO wait, which is where a healthy * idle core sits (dev notes §1: parking a resident triad breaks its NEXT boot). */ release_program: free(row); free(scratch); ft_tt_program_directory_release(&g_program); release: ft_tt_firmware_set_release(&firmware); ft_tt_hardware_session_close(&session); return ft_tt_test_finish(); }