Foundry/tt/tools/ft_tt_core_probe.c

388 lines
14 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_core_probe.c
* @brief Implementation of the one-core health test: boot, run a vector-add triad, compare.
*
* The run itself is the Phase 2 checklist step 12 shape (tests/test_phase2_vector_add.c),
* reduced to what a health check needs and with the L1 map DERIVED from where the firmware
* images landed rather than chosen.
*
* @status Runs on the p150a test card.
* @issues None
* @todo None
*/
#include "ft_tt_core_probe.h"
#include <stdio.h>
#include <string.h>
#include "ft_tt_phase2_l1_layout.h"
#define PROBE_RUN_TIMEOUT_MS 5000u
#define PROBE_TILE_ELEMENTS (FT_TT_PHASE2_TILE_BYTES / 2u)
#define PROBE_CB_CONFIG_BYTES (FT_TT_NUM_CIRCULAR_BUFFERS * FT_TT_WORDS_PER_LOCAL_CB_CONFIG * 4u)
/** Every processor: the triad is reader on BRISC, writer on NCRISC, compute on all three
* TRISCs. A health check that left the backend out could not see the failure that
* damaged 14 cores. */
#define PROBE_PROCESSOR_MASK 0x1fu
static uint16_t tile_in0[PROBE_TILE_ELEMENTS];
static uint16_t tile_in1[PROBE_TILE_ELEMENTS];
static uint16_t tile_expected[PROBE_TILE_ELEMENTS];
static uint16_t tile_readback[PROBE_TILE_ELEMENTS];
/** bfloat16 is the top 16 bits of the binary32 pattern, truncated. */
static uint16_t bfloat16_from_float(float value)
{
uint32_t bits;
memcpy(&bits, &value, sizeof(bits));
return (uint16_t)(bits >> 16);
}
/**
* Build both operand tiles and the reference for one tile index.
*
* Small integers, so every sum is exactly representable in bfloat16 and "byte-exact" is
* a fair demand rather than a rounding lottery. The per-tile offset is added OUTSIDE the
* modulus so two tiles never collapse onto identical data.
*/
static void build_reference_tiles(unsigned tile_index)
{
unsigned element;
for (element = 0; element < PROBE_TILE_ELEMENTS; element++) {
float left = (float)((element % 16u) + tile_index + 1u);
float right = (float)(((element * 3u) % 16u) + tile_index + 1u);
tile_in0[element] = bfloat16_from_float(left);
tile_in1[element] = bfloat16_from_float(right);
tile_expected[element] = bfloat16_from_float(left + right);
}
}
static unsigned first_difference(const uint16_t *left, const uint16_t *right)
{
unsigned element;
for (element = 0; element < PROBE_TILE_ELEMENTS; element++)
if (left[element] != right[element])
return element;
return PROBE_TILE_ELEMENTS;
}
/** Describe the three circular buffers the vector_add triad was built against. */
static int write_cb_config(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_phase2_l1_layout *layout, uint64_t *out_mask)
{
static uint8_t buffer[PROBE_CB_CONFIG_BYTES];
const uint32_t addresses[3] = {layout->cb_in0_address, layout->cb_in1_address,
layout->cb_out_address};
static const unsigned indices[3] = {FT_TT_PHASE2_CB_IN0, FT_TT_PHASE2_CB_IN1,
FT_TT_PHASE2_CB_OUT};
ft_tt_launch_cb_entry entries[3];
unsigned entry;
int rc;
for (entry = 0; entry < 3u; entry++) {
entries[entry].index = indices[entry];
entries[entry].address = addresses[entry];
entries[entry].size_bytes = FT_TT_PHASE2_CB_BYTES;
entries[entry].page_size_bytes = FT_TT_PHASE2_TILE_BYTES;
entries[entry].page_count = FT_TT_PHASE2_CB_PAGE_COUNT;
}
rc = ft_tt_launch_encode_cb_config(entries, 3u, buffer, sizeof(buffer), out_mask);
if (rc != FT_TT_OK)
return rc;
return noc->write(noc->user, x, y, layout->cb_config_address, buffer, sizeof(buffer));
}
static void build_launch_config(const ft_tt_phase2_l1_layout *layout,
const ft_tt_image_placement placements[FT_TT_PROC_COUNT],
uint64_t cb_mask, ft_tt_launch_config *out_config)
{
unsigned processor;
memset(out_config, 0, sizeof(*out_config));
out_config->kernel_config_base = layout->kernel_config_base;
out_config->enables = PROBE_PROCESSOR_MASK;
out_config->local_cb_config_address = layout->cb_config_address;
out_config->local_cb_mask = cb_mask;
/* vector_add_reader.c is compiled with NOC_INDEX=0 and BRISC firmware re-reads this
* at run time. The two MUST agree. */
out_config->brisc_noc_id = 0u;
for (processor = 0; processor < FT_TT_PROC_COUNT; processor++) {
out_config->kernel_text_address[processor] = placements[processor].entry_address;
out_config->kernel_text_bytes[processor] = placements[processor].text_bytes;
out_config->runtime_args_address[processor] =
ft_tt_phase2_runtime_args_address(layout, processor);
}
}
static int plant_runtime_args(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_phase2_l1_layout *layout,
const ft_tt_launch_config *config)
{
uint32_t reader_arguments[7];
uint32_t writer_arguments[4];
uint32_t tile_count = FT_TT_CORE_PROBE_TILE_COUNT;
unsigned processor;
int rc;
reader_arguments[0] = tile_count;
reader_arguments[1] = x;
reader_arguments[2] = y;
reader_arguments[3] = layout->source0_address;
reader_arguments[4] = x;
reader_arguments[5] = y;
reader_arguments[6] = layout->source1_address;
writer_arguments[0] = tile_count;
writer_arguments[1] = x;
writer_arguments[2] = y;
writer_arguments[3] = layout->destination_address;
rc = ft_tt_launch_plant_runtime_args(noc, x, y, config->runtime_args_address[FT_TT_PROC_DM0],
reader_arguments, 7u);
if (rc == FT_TT_OK)
rc = ft_tt_launch_plant_runtime_args(noc, x, y,
config->runtime_args_address[FT_TT_PROC_DM1],
writer_arguments, 4u);
/* Every enabled TRISC needs a VALID runtime-arg block, not just the one you care
* about — a stale block stalled a core once (dev notes §5). */
for (processor = FT_TT_PROC_MATH0; processor < FT_TT_PROC_COUNT && rc == FT_TT_OK; processor++)
rc = ft_tt_launch_plant_runtime_args(noc, x, y, config->runtime_args_address[processor],
&tile_count, 1u);
return rc;
}
static int stage_sources(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_phase2_l1_layout *layout)
{
unsigned tile;
for (tile = 0; tile < FT_TT_CORE_PROBE_TILE_COUNT; tile++) {
uint32_t offset = tile * FT_TT_PHASE2_TILE_BYTES;
int rc;
build_reference_tiles(tile);
rc = noc->write(noc->user, x, y, layout->source0_address + offset, tile_in0,
sizeof(tile_in0));
if (rc != FT_TT_OK)
return rc;
rc = noc->write(noc->user, x, y, layout->source1_address + offset, tile_in1,
sizeof(tile_in1));
if (rc != FT_TT_OK)
return rc;
}
return FT_TT_OK;
}
/** Wipe the destination so a run that did nothing reads as zeros, not as last run's answer. */
static int wipe_destination(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_phase2_l1_layout *layout)
{
unsigned tile;
memset(tile_readback, 0, sizeof(tile_readback));
for (tile = 0; tile < FT_TT_CORE_PROBE_TILE_COUNT; tile++) {
int rc = noc->write(noc->user, x, y,
layout->destination_address + tile * FT_TT_PHASE2_TILE_BYTES,
tile_readback, sizeof(tile_readback));
if (rc != FT_TT_OK)
return rc;
}
return FT_TT_OK;
}
static int check_destination(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_phase2_l1_layout *layout, unsigned *out_bad_tile,
unsigned *out_bad_element)
{
unsigned tile;
*out_bad_tile = FT_TT_CORE_PROBE_TILE_COUNT;
*out_bad_element = PROBE_TILE_ELEMENTS;
for (tile = 0; tile < FT_TT_CORE_PROBE_TILE_COUNT; tile++) {
unsigned difference;
int rc = noc->read(noc->user, x, y,
layout->destination_address + tile * FT_TT_PHASE2_TILE_BYTES,
tile_readback, sizeof(tile_readback));
if (rc != FT_TT_OK)
return rc;
build_reference_tiles(tile);
difference = first_difference(tile_readback, tile_expected);
if (difference != PROBE_TILE_ELEMENTS) {
*out_bad_tile = tile;
*out_bad_element = difference;
return FT_TT_ERROR;
}
}
return FT_TT_OK;
}
static int launch_and_wait(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
const ft_tt_launch_config *config)
{
int rc = ft_tt_launch_write_launch_message(noc, x, y, config);
if (rc != FT_TT_OK)
return rc;
rc = ft_tt_launch_send_go(noc, x, y);
if (rc != FT_TT_OK)
return rc;
return ft_tt_launch_wait_done(noc, x, y, PROBE_RUN_TIMEOUT_MS, NULL);
}
bool ft_tt_core_probe_passed(const ft_tt_core_probe_result *result)
{
return result && !result->refused && result->repeats_requested != 0u &&
result->repeats_passed == result->repeats_requested;
}
int ft_tt_core_probe_context_open(const char *directory, ft_tt_core_probe_context *out)
{
int rc;
if (!directory || !out)
return FT_TT_EINVAL;
memset(out, 0, sizeof(*out));
rc = ft_tt_firmware_set_load(directory, &out->firmware);
if (rc != FT_TT_OK)
return rc;
rc = ft_tt_program_directory_load(directory, PROBE_PROCESSOR_MASK, &out->program);
if (rc != FT_TT_OK) {
ft_tt_firmware_set_release(&out->firmware);
return rc;
}
return FT_TT_OK;
}
void ft_tt_core_probe_context_close(ft_tt_core_probe_context *context)
{
if (!context)
return;
ft_tt_program_directory_release(&context->program);
ft_tt_firmware_set_release(&context->firmware);
}
/** Record a failure and hand back FT_TT_OK: a failed probe is a RESULT, not an error. */
static int fail(ft_tt_core_probe_result *out, const char *stage, int code)
{
out->failure_stage = stage;
out->failure_code = code;
return FT_TT_OK;
}
int ft_tt_core_probe_run(const ft_tt_noc_access *noc, const ft_tt_grid_info *grid,
ft_tt_core_probe_context *context, const ft_tt_core_status *status,
unsigned repeats, bool force, ft_tt_core_probe_result *out)
{
ft_tt_image_placement firmware_placements[FT_TT_PROC_COUNT];
ft_tt_image_placement kernel_placements[FT_TT_PROC_COUNT];
ft_tt_phase2_l1_layout layout;
ft_tt_launch_config config;
ft_tt_noc_coord core;
uint64_t cb_mask = 0;
unsigned completed = 0;
int rc;
if (!noc || !grid || !context || !status || !out || repeats == 0u)
return FT_TT_EINVAL;
memset(out, 0, sizeof(*out));
out->index = status->index;
out->repeats_requested = repeats;
out->first_bad_tile = FT_TT_CORE_PROBE_TILE_COUNT;
out->first_bad_element = PROBE_TILE_ELEMENTS;
/* Resolve independently of the status read rather than trusting its coordinate: the
* one thing that must never be second-hand is the endpoint (spec §7.3). */
rc = ft_tt_coord_tensix_by_index(grid, status->index, FT_TT_COORD_TRANSLATED, &core);
if (rc != FT_TT_OK)
return rc;
out->x = (uint16_t)core.x;
out->y = (uint16_t)core.y;
/* Booting starts with assert_reset. Doing that to a core with a kernel still in it is
* the exact move that wedged Tensix 0 (dev notes §1), so it is refused here. */
if (!force &&
(status->verdict == FT_TT_CORE_VERDICT_KERNEL_RUNNING || status->queue_heartbeat_ticking)) {
out->refused = true;
out->failure_stage = "refused: a kernel is running on this core";
return FT_TT_EPERM;
}
rc = ft_tt_core_boot_firmware(noc, out->x, out->y, &context->firmware, firmware_placements);
if (rc != FT_TT_OK)
return fail(out, "firmware boot", rc);
out->firmware_booted = true;
rc = ft_tt_phase2_l1_layout_derive(firmware_placements, FT_TT_PROC_COUNT, &layout);
if (rc != FT_TT_OK)
return fail(out, "L1 layout", rc);
rc = ft_tt_core_load_program(noc, out->x, out->y, &context->program.program,
kernel_placements);
if (rc != FT_TT_OK)
return fail(out, "kernel load", rc);
out->program_loaded = true;
rc = write_cb_config(noc, out->x, out->y, &layout, &cb_mask);
if (rc != FT_TT_OK)
return fail(out, "CB config", rc);
build_launch_config(&layout, kernel_placements, cb_mask, &config);
rc = plant_runtime_args(noc, out->x, out->y, &layout, &config);
if (rc != FT_TT_OK)
return fail(out, "runtime args", rc);
rc = stage_sources(noc, out->x, out->y, &layout);
if (rc != FT_TT_OK)
return fail(out, "staging", rc);
while (completed < repeats) {
rc = wipe_destination(noc, out->x, out->y, &layout);
if (rc != FT_TT_OK)
return fail(out, "destination wipe", rc);
rc = launch_and_wait(noc, out->x, out->y, &config);
if (rc != FT_TT_OK)
return fail(out, "kernel did not complete", rc);
out->completed = true;
rc = check_destination(noc, out->x, out->y, &layout, &out->first_bad_tile,
&out->first_bad_element);
if (rc != FT_TT_OK)
return fail(out, "result is not byte-exact", rc);
completed++;
out->repeats_passed = completed;
}
/* The core is left with firmware up and idle. NOT parked — see the file header. */
return FT_TT_OK;
}
void ft_tt_core_probe_print_line(const ft_tt_core_probe_result *result)
{
if (!result)
return;
if (ft_tt_core_probe_passed(result)) {
printf("%5u (%2u,%2u) PASS %u/%u runs byte-exact, %u tiles x %u bf16 elements\n",
result->index, result->x, result->y, result->repeats_passed,
result->repeats_requested, FT_TT_CORE_PROBE_TILE_COUNT, PROBE_TILE_ELEMENTS);
return;
}
printf("%5u (%2u,%2u) %s %s (rc=%d)", result->index, result->x, result->y,
result->refused ? "SKIP" : "FAIL",
result->failure_stage ? result->failure_stage : "?", result->failure_code);
if (result->first_bad_tile != FT_TT_CORE_PROBE_TILE_COUNT)
printf(", first difference tile %u element %u", result->first_bad_tile,
result->first_bad_element);
printf(", %u/%u runs passed\n", result->repeats_passed, result->repeats_requested);
}