235 lines
8.8 KiB
C
235 lines
8.8 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* test_first_write.c — the first bytes this backend ever writes to the card.
|
|
*
|
|
* Run one step at a time and inspect between them. The steps are ordered by how
|
|
* much they can teach and how much they can cost:
|
|
*
|
|
* arc re-read ARC telemetry. Already proven, so this is a free check that
|
|
* nothing drifted since the last run. Reads only.
|
|
* tensix write a pattern into one derived Tensix's L1 and read it back. This
|
|
* is the first write. On THIS card all 14 columns are enabled, so
|
|
* TRANSLATED == NOC0 for every Tensix — which is exactly why this step
|
|
* cannot prove the translation layer. It proves the write path.
|
|
* dram write a pattern into DRAM bank 0 and read it back. This IS the
|
|
* translation proof: translated DRAM lives at (17-18, 12-23), which
|
|
* exists nowhere in NoC0 space, so if
|
|
* "TLBs are aimed with TRANSLATED coordinates"
|
|
* (docs/tt_driver_notes.md §8) is wrong,
|
|
* this is where it shows up.
|
|
*
|
|
* Every coordinate is DERIVED from ft_tt_coord against the live grid. Nothing
|
|
* here is hardcoded or probed — a wrong endpoint hangs the chip and costs a
|
|
* physical power cycle (spec §7.3), and that has already happened once.
|
|
*
|
|
* Build (the fence must be opened explicitly):
|
|
* cc -std=c99 -Wall -Wextra -Werror -DFT_TT_PHASE1B_NOC_ENABLED -I.. \
|
|
* ../ft_tt_kmd.c ../ft_tt_device.c ../ft_tt_tlb.c \
|
|
* ../ft_tt_arc_telemetry.c ../ft_tt_coord.c test_first_write.c \
|
|
* -o build/test_first_write
|
|
*/
|
|
|
|
#include "ft_tt_arc_telemetry.h"
|
|
#include "ft_tt_coord.h"
|
|
#include "ft_tt_device.h"
|
|
#include "ft_tt_tlb.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/* A 2 MiB window must be aimed at a 2 MiB-aligned address.
|
|
* L1 is 1536 KiB, so aiming at 0 covers all of it and we index inside. */
|
|
#define WINDOW_BYTES (2u * 1024u * 1024u)
|
|
#define L1_AIM_BASE 0u
|
|
#define L1_SCRATCH_OFF 0x100000u /* 1 MiB into L1: clear of mailbox and firmware */
|
|
#define DRAM_AIM_BASE 0x200000u /* 2 MiB into the bank, 2 MiB aligned */
|
|
|
|
#define PATTERN_WORDS 64u /* 256 bytes — a multiple of the 16 B access rule */
|
|
|
|
static int failures;
|
|
|
|
static void check(const char *what, bool ok, const char *detail)
|
|
{
|
|
printf(" %-4s %-46s %s\n", ok ? "ok" : "FAIL", what, detail ? detail : "");
|
|
if (!ok) {
|
|
failures++;
|
|
}
|
|
}
|
|
|
|
/** Fill a distinctive, position-dependent pattern so a partial or shifted
|
|
* write is visible rather than looking like a clean success. */
|
|
static void fill_pattern(uint32_t *buf, uint32_t seed)
|
|
{
|
|
for (uint32_t i = 0; i < PATTERN_WORDS; i++) {
|
|
buf[i] = seed ^ (i * 0x9E3779B9u);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Aim `window` at (coord, aim_base), write `PATTERN_WORDS` words at
|
|
* `offset` inside the aperture, read them back and compare.
|
|
*/
|
|
static int write_read_back(ft_tt_ctx *ctx, ft_tt_tlb_window *window,
|
|
ft_tt_noc_coord coord, uint64_t aim_base, uint32_t offset,
|
|
uint32_t seed, const char *label)
|
|
{
|
|
char detail[160];
|
|
|
|
int rc = ft_tt_tlb_window_aim(ctx, window, (uint16_t)coord.x, (uint16_t)coord.y, aim_base);
|
|
snprintf(detail, sizeof(detail), "(%u, %u) base 0x%llx rc=%d", coord.x, coord.y,
|
|
(unsigned long long)aim_base, rc);
|
|
check("window aimed", rc == FT_TT_OK, detail);
|
|
if (rc != FT_TT_OK) {
|
|
return rc;
|
|
}
|
|
|
|
volatile unsigned char *aperture = ft_tt_tlb_window_aperture(window);
|
|
if (aperture == NULL) {
|
|
check("aperture is mapped", false, "NULL");
|
|
return FT_TT_EINVAL;
|
|
}
|
|
|
|
uint32_t expected[PATTERN_WORDS];
|
|
uint32_t got[PATTERN_WORDS];
|
|
fill_pattern(expected, seed);
|
|
memset(got, 0, sizeof(got));
|
|
|
|
volatile uint32_t *target = (volatile uint32_t *)(void *)(aperture + offset);
|
|
|
|
/* Read the region first, purely for the log: if it already contains our
|
|
* pattern from a previous run, a no-op write would look like success. */
|
|
uint32_t before = target[0];
|
|
|
|
for (uint32_t i = 0; i < PATTERN_WORDS; i++) {
|
|
target[i] = expected[i];
|
|
}
|
|
for (uint32_t i = 0; i < PATTERN_WORDS; i++) {
|
|
got[i] = target[i];
|
|
}
|
|
|
|
bool match = memcmp(expected, got, sizeof(expected)) == 0;
|
|
if (match) {
|
|
snprintf(detail, sizeof(detail), "%u words at +0x%x (was 0x%08x, now 0x%08x)",
|
|
PATTERN_WORDS, offset, before, got[0]);
|
|
} else {
|
|
uint32_t first = 0;
|
|
while (first < PATTERN_WORDS && expected[first] == got[first]) {
|
|
first++;
|
|
}
|
|
snprintf(detail, sizeof(detail), "mismatch at word %u: wrote 0x%08x read 0x%08x", first,
|
|
expected[first], got[first]);
|
|
}
|
|
check(label, match, detail);
|
|
|
|
return match ? FT_TT_OK : FT_TT_EIO;
|
|
}
|
|
|
|
static int step_arc(ft_tt_ctx *ctx, ft_tt_grid_info *grid)
|
|
{
|
|
int rc = ft_tt_arc_telemetry_read_grid(ctx, grid);
|
|
char detail[128];
|
|
snprintf(detail, sizeof(detail), "rc=%d", rc);
|
|
check("ARC telemetry re-read", rc == FT_TT_OK, detail);
|
|
if (rc != FT_TT_OK) {
|
|
return rc;
|
|
}
|
|
|
|
snprintf(detail, sizeof(detail), "cols=0x%x (%u), gddr=%u banks", grid->tensix_enabled_col,
|
|
grid->tensix_column_count, grid->dram_bank_count);
|
|
check("topology unchanged", grid->tensix_enabled_col == 16383u && grid->dram_bank_count == 8u,
|
|
detail);
|
|
|
|
rc = ft_tt_coord_check_grid(grid);
|
|
snprintf(detail, sizeof(detail), "rc=%d", rc);
|
|
check("grid is expressible by the coordinate map", rc == FT_TT_OK, detail);
|
|
return rc;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *step = (argc > 1) ? argv[1] : "arc";
|
|
|
|
ft_tt_ctx *ctx = ft_tt_init_device(0);
|
|
if (ctx == NULL) {
|
|
fprintf(stderr, "could not open device 0\n");
|
|
return 1;
|
|
}
|
|
|
|
printf("=== step: %s ===\n", step);
|
|
|
|
ft_tt_grid_info grid;
|
|
int rc = step_arc(ctx, &grid);
|
|
if (rc != FT_TT_OK || failures != 0) {
|
|
printf("\nstopping: the grid must be sound before anything is written\n");
|
|
ft_tt_ctx_free(ctx);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(step, "arc") == 0) {
|
|
printf("\n%d checks failed\n", failures);
|
|
ft_tt_ctx_free(ctx);
|
|
return failures == 0 ? 0 : 1;
|
|
}
|
|
|
|
ft_tt_tlb_window *window = NULL;
|
|
rc = ft_tt_tlb_window_alloc(ctx, WINDOW_BYTES, FT_TT_KMD_CACHE_UC, &window);
|
|
if (rc != FT_TT_OK) {
|
|
fprintf(stderr, "window alloc failed: %d\n", rc);
|
|
ft_tt_ctx_free(ctx);
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(step, "tensix") == 0) {
|
|
ft_tt_noc_coord noc0;
|
|
ft_tt_noc_coord translated;
|
|
char detail[160];
|
|
|
|
rc = ft_tt_coord_tensix_by_index(&grid, 0, FT_TT_COORD_NOC0, &noc0);
|
|
int rc2 = ft_tt_coord_tensix_by_index(&grid, 0, FT_TT_COORD_TRANSLATED, &translated);
|
|
snprintf(detail, sizeof(detail), "noc0 (%u, %u), translated (%u, %u)", noc0.x, noc0.y,
|
|
translated.x, translated.y);
|
|
check("Tensix 0 derived in both systems", rc == FT_TT_OK && rc2 == FT_TT_OK, detail);
|
|
|
|
/* On a 14/14 card these must agree. If they do not, the mapping is
|
|
* wrong and nothing should be written. */
|
|
check("translated == noc0 on this un-harvested card",
|
|
noc0.x == translated.x && noc0.y == translated.y, detail);
|
|
|
|
if (failures == 0) {
|
|
(void)write_read_back(ctx, window, translated, L1_AIM_BASE, L1_SCRATCH_OFF,
|
|
0xA5A5A5A5u, "L1 pattern survives write -> read");
|
|
}
|
|
} else if (strcmp(step, "dram") == 0) {
|
|
ft_tt_noc_coord noc0;
|
|
ft_tt_noc_coord translated;
|
|
char detail[160];
|
|
|
|
rc = ft_tt_coord_dram_bank(&grid, 0, FT_TT_COORD_NOC0, &noc0);
|
|
int rc2 = ft_tt_coord_dram_bank(&grid, 0, FT_TT_COORD_TRANSLATED, &translated);
|
|
snprintf(detail, sizeof(detail), "noc0 (%u, %u), translated (%u, %u)", noc0.x, noc0.y,
|
|
translated.x, translated.y);
|
|
check("DRAM bank 0 derived in both systems", rc == FT_TT_OK && rc2 == FT_TT_OK, detail);
|
|
|
|
/* The translation proof: these MUST differ, and translated must be in
|
|
* the alias grid (x 17-18), which is not a NoC0 address at all. */
|
|
check("translated differs from noc0 — this is the alias grid",
|
|
!(noc0.x == translated.x && noc0.y == translated.y), detail);
|
|
check("translated DRAM x is in the alias range 17-18",
|
|
translated.x == 17u || translated.x == 18u, detail);
|
|
|
|
if (failures == 0) {
|
|
(void)write_read_back(ctx, window, translated, DRAM_AIM_BASE, 0u, 0x5A5A5A5Au,
|
|
"DRAM pattern survives write -> read");
|
|
}
|
|
} else {
|
|
fprintf(stderr, "unknown step '%s' (use: arc | tensix | dram)\n", step);
|
|
failures++;
|
|
}
|
|
|
|
(void)ft_tt_tlb_window_free(ctx, window);
|
|
printf("\n%d checks failed\n", failures);
|
|
ft_tt_ctx_free(ctx);
|
|
return failures == 0 ? 0 : 1;
|
|
}
|