666 lines
27 KiB
C
666 lines
27 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* test_phase2_dram.c — the first NoC traffic a kernel has issued that LEAVES the core.
|
|
*
|
|
* Plan Phase 2 follow-up 6. Everything Foundry has run on the card so far put every
|
|
* buffer in the target core's own L1: step 12's reader and writer were pointed at the
|
|
* core they run on, so the whole reader/writer split — and Phase 5's spatial thesis on
|
|
* top of it — has never moved a byte between two endpoints. This test moves the source
|
|
* data to **DRAM bank 0** and makes the reader fetch it over the NoC.
|
|
*
|
|
* Two stages, in this order, because they differ by which DIRECTION crosses the fabric:
|
|
*
|
|
* A sources in DRAM, destination in this core's L1. The reader issues a real
|
|
* cross-endpoint READ; nothing the device writes leaves the core. If the DRAM
|
|
* endpoint were wrong this is the cheapest way to find out.
|
|
* B sources in DRAM, destination in DRAM. Adds the writer's cross-endpoint WRITE,
|
|
* on the other NoC, so both directions and both data-movement cores are covered.
|
|
*
|
|
* Every coordinate comes from ft_tt_coord against live ARC telemetry — the Tensix from
|
|
* ft_tt_hardware_session_tensix() and the DRAM bank from ft_tt_coord_dram_bank() in
|
|
* TRANSLATED form, which is the space a TLB window is aimed with and, on Blackhole,
|
|
* the space that goes on the wire unmodified on both NoCs (DYNAMIC_NOC_X/Y are the
|
|
* identity). Nothing here is probed: a wrong endpoint does
|
|
* not fail, it hangs the chip (spec §7.3).
|
|
*
|
|
* Byte-exactness alone would not prove the data came from DRAM — a stale CB could
|
|
* produce the same answer. So the run is repeated against three different DRAM
|
|
* contents, including zeros, and the result must track every one of them. The zeroed
|
|
* pass is the red half (coding-best-practices.md §9): if the output stayed correct with
|
|
* the DRAM source wiped, the reader was reading something else.
|
|
*
|
|
* Usage: test_phase2_dram <program-directory>
|
|
* The directory is a build_tensix_program.sh output for the vector_add triad at the
|
|
* default 2048-byte tile, e.g. programs/vector_add.
|
|
*
|
|
* Build:
|
|
* make -f Makefile.phase2_followups PHASE1B=1 build/test_phase2_dram
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "ft_tt_buffer.h"
|
|
#include "ft_tt_hardware_session.h"
|
|
#include "ft_tt_noc.h"
|
|
#include "ft_tt_phase2_l1_layout.h"
|
|
#include "ft_tt_program_directory.h"
|
|
#include "ft_tt_test_report.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#define RUN_TIMEOUT_MS 5000u
|
|
#define TILE_COUNT 2u
|
|
#define TILE_ELEMENTS (FT_TT_PHASE2_TILE_BYTES / 2u)
|
|
#define BUFFER_BYTES (TILE_COUNT * FT_TT_PHASE2_TILE_BYTES)
|
|
#define CB_CONFIG_BYTES (FT_TT_NUM_CIRCULAR_BUFFERS * FT_TT_WORDS_PER_LOCAL_CB_CONFIG * 4u)
|
|
|
|
/** Repeats of the DRAM-to-DRAM stage. Enough to catch a path that works once and leaks
|
|
* state, cheap enough that a failure is found in seconds. */
|
|
#define REPEAT_COUNT 100u
|
|
|
|
/** DRAM bank this test owns. Never derive a second one by arithmetic on this. */
|
|
#define DRAM_LOGICAL_BANK 0u
|
|
|
|
/** The L1 map, derived from where the firmware images landed rather than chosen. A
|
|
* file-scope object because every helper below needs it and threading one more
|
|
* parameter through all of them would say less than this comment does. Written once,
|
|
* in main(), before anything reads it. */
|
|
static ft_tt_phase2_l1_layout g_layout;
|
|
|
|
/**
|
|
* Boot the firmware and DERIVE the L1 map from where its images landed.
|
|
*
|
|
* The map's base used to be the literal 0x14000. It is now the higher of "the top of the
|
|
* five firmware images the loader just placed" and "the end of the regions the stock memory map
|
|
* reserves above MEM_MAP_END", which is exactly what ft_tt_image_placement_l1_floor()
|
|
* was written to compute and had no caller for.
|
|
*/
|
|
static int boot_and_derive_layout(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
|
|
const ft_tt_firmware_set *firmware)
|
|
{
|
|
ft_tt_image_placement placements[FT_TT_PROC_COUNT];
|
|
int rc = ft_tt_core_boot_firmware(noc, x, y, firmware, placements);
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
return ft_tt_phase2_l1_layout_derive(placements, FT_TT_PROC_COUNT, &g_layout);
|
|
}
|
|
|
|
static uint16_t tile_in0[TILE_ELEMENTS];
|
|
static uint16_t tile_in1[TILE_ELEMENTS];
|
|
static uint16_t tile_expected[TILE_ELEMENTS];
|
|
static uint16_t tile_readback[TILE_ELEMENTS];
|
|
|
|
/** Where the three DRAM buffers of one run live, plus the endpoint they live at. */
|
|
typedef struct dram_region {
|
|
ft_tt_noc_coord coord;
|
|
ft_tt_buffer source0;
|
|
ft_tt_buffer source1;
|
|
ft_tt_buffer destination;
|
|
} dram_region;
|
|
|
|
/** bfloat16 is the top 16 bits of the binary32 pattern. Every value here is a small
|
|
* integer, so the truncation and the sum are both exact. */
|
|
static uint16_t bfloat16_from_float(float value)
|
|
{
|
|
uint32_t bits;
|
|
|
|
memcpy(&bits, &value, sizeof(bits));
|
|
return (uint16_t)(bits >> 16);
|
|
}
|
|
|
|
/**
|
|
* Build the two input tiles and the reference result for one tile of one pass.
|
|
*
|
|
* `pass` shifts the values, so two passes over the same buffers cannot pass on each
|
|
* other's answer. PASS_ZEROED builds an all-zero input, whose reference is all-zero too.
|
|
*/
|
|
#define PASS_ZEROED 0xFFFFu
|
|
|
|
static void build_reference_tiles(unsigned tile_index, unsigned pass)
|
|
{
|
|
unsigned element;
|
|
|
|
for (element = 0; element < TILE_ELEMENTS; element++) {
|
|
float left;
|
|
float right;
|
|
|
|
if (pass == PASS_ZEROED) {
|
|
tile_in0[element] = 0u;
|
|
tile_in1[element] = 0u;
|
|
tile_expected[element] = 0u;
|
|
continue;
|
|
}
|
|
left = (float)((element + tile_index + pass) % 16u + 1u);
|
|
right = (float)((element * 3u + tile_index + pass * 5u) % 16u + 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 < TILE_ELEMENTS; element++) {
|
|
if (left[element] != right[element])
|
|
return element;
|
|
}
|
|
return TILE_ELEMENTS;
|
|
}
|
|
|
|
/** Write both source tile sets into DRAM and wipe the DRAM destination. */
|
|
static int stage_dram_sources(ft_tt_noc_path *path, const dram_region *region, unsigned pass)
|
|
{
|
|
unsigned tile;
|
|
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
uint64_t offset = (uint64_t)tile * FT_TT_PHASE2_TILE_BYTES;
|
|
int rc;
|
|
|
|
build_reference_tiles(tile, pass);
|
|
rc = ft_tt_write_dram(path, region->coord, region->source0.address + offset, tile_in0,
|
|
sizeof(tile_in0));
|
|
if (rc == FT_TT_OK)
|
|
rc = ft_tt_write_dram(path, region->coord, region->source1.address + offset, tile_in1,
|
|
sizeof(tile_in1));
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** Prove the host's own view of DRAM before asking a kernel to read it. */
|
|
static int verify_dram_sources(ft_tt_noc_path *path, const dram_region *region, unsigned pass)
|
|
{
|
|
unsigned tile;
|
|
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
uint64_t offset = (uint64_t)tile * FT_TT_PHASE2_TILE_BYTES;
|
|
int rc = ft_tt_read_dram(path, region->coord, region->source0.address + offset,
|
|
tile_readback, sizeof(tile_readback));
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
build_reference_tiles(tile, pass);
|
|
if (first_difference(tile_readback, tile_in0) != TILE_ELEMENTS)
|
|
return FT_TT_ERROR;
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** Fill the DRAM destination with a value the kernel must overwrite. */
|
|
static int poison_dram_destination(ft_tt_noc_path *path, const dram_region *region)
|
|
{
|
|
unsigned tile;
|
|
|
|
memset(tile_readback, 0xA5, sizeof(tile_readback));
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
int rc = ft_tt_write_dram(path, region->coord,
|
|
region->destination.address +
|
|
(uint64_t)tile * FT_TT_PHASE2_TILE_BYTES,
|
|
tile_readback, sizeof(tile_readback));
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** Wipe the L1 destination the stage-A run writes into. */
|
|
static int wipe_l1_destination(const ft_tt_noc_access *noc, uint16_t x, uint16_t y)
|
|
{
|
|
unsigned tile;
|
|
|
|
memset(tile_readback, 0, sizeof(tile_readback));
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
int rc = noc->write(noc->user, x, y,
|
|
g_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_l1_destination(const ft_tt_noc_access *noc, uint16_t x, uint16_t y, unsigned pass,
|
|
unsigned *out_bad_tile, unsigned *out_bad_element)
|
|
{
|
|
unsigned tile;
|
|
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
unsigned difference;
|
|
int rc = noc->read(noc->user, x, y,
|
|
g_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, pass);
|
|
difference = first_difference(tile_readback, tile_expected);
|
|
if (difference != TILE_ELEMENTS) {
|
|
*out_bad_tile = tile;
|
|
*out_bad_element = difference;
|
|
return FT_TT_ERROR;
|
|
}
|
|
}
|
|
*out_bad_tile = TILE_COUNT;
|
|
*out_bad_element = TILE_ELEMENTS;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
static int check_dram_destination(ft_tt_noc_path *path, const dram_region *region, unsigned pass,
|
|
unsigned *out_bad_tile, unsigned *out_bad_element)
|
|
{
|
|
unsigned tile;
|
|
|
|
for (tile = 0; tile < TILE_COUNT; tile++) {
|
|
unsigned difference;
|
|
int rc = ft_tt_read_dram(path, region->coord,
|
|
region->destination.address +
|
|
(uint64_t)tile * FT_TT_PHASE2_TILE_BYTES,
|
|
tile_readback, sizeof(tile_readback));
|
|
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
build_reference_tiles(tile, pass);
|
|
difference = first_difference(tile_readback, tile_expected);
|
|
if (difference != TILE_ELEMENTS) {
|
|
*out_bad_tile = tile;
|
|
*out_bad_element = difference;
|
|
return FT_TT_ERROR;
|
|
}
|
|
}
|
|
*out_bad_tile = TILE_COUNT;
|
|
*out_bad_element = TILE_ELEMENTS;
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
/** Describe the three circular buffers, all still in this core's L1. */
|
|
static int write_cb_config(const ft_tt_noc_access *noc, uint16_t x, uint16_t y, uint64_t *out_mask)
|
|
{
|
|
const uint32_t addresses[3] = {g_layout.cb_in0_address, g_layout.cb_in1_address,
|
|
g_layout.cb_out_address};
|
|
static const unsigned indices[3] = {FT_TT_PHASE2_CB_IN0, FT_TT_PHASE2_CB_IN1,
|
|
FT_TT_PHASE2_CB_OUT};
|
|
static uint8_t buffer[CB_CONFIG_BYTES];
|
|
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, g_layout.cb_config_address, buffer, sizeof(buffer));
|
|
}
|
|
|
|
static void build_config(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 = g_layout.kernel_config_base;
|
|
out_config->enables = 0x1fu;
|
|
out_config->local_cb_config_address = g_layout.cb_config_address;
|
|
out_config->local_cb_mask = cb_mask;
|
|
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(&g_layout, processor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plant runtime args for one stage.
|
|
*
|
|
* The reader always names the DRAM endpoint; the writer names whatever
|
|
* `destination_*` says, which is how stage A and stage B differ.
|
|
*/
|
|
static int plant_runtime_args(const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
|
|
const ft_tt_launch_config *config, const dram_region *region,
|
|
uint32_t destination_x, uint32_t destination_y,
|
|
uint32_t destination_address)
|
|
{
|
|
uint32_t reader_arguments[7];
|
|
uint32_t writer_arguments[4];
|
|
uint32_t tile_count = TILE_COUNT;
|
|
unsigned processor;
|
|
int rc;
|
|
|
|
reader_arguments[0] = tile_count;
|
|
reader_arguments[1] = region->coord.x;
|
|
reader_arguments[2] = region->coord.y;
|
|
reader_arguments[3] = (uint32_t)region->source0.address;
|
|
reader_arguments[4] = region->coord.x;
|
|
reader_arguments[5] = region->coord.y;
|
|
reader_arguments[6] = (uint32_t)region->source1.address;
|
|
|
|
writer_arguments[0] = tile_count;
|
|
writer_arguments[1] = destination_x;
|
|
writer_arguments[2] = destination_y;
|
|
writer_arguments[3] = 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);
|
|
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 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, RUN_TIMEOUT_MS, NULL);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Stage A — the reader crosses the fabric, the writer does not */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static void run_stage_a_dram_to_l1(const ft_tt_noc_access *noc, ft_tt_noc_path *path, uint16_t x,
|
|
uint16_t y,
|
|
const ft_tt_image_placement placements[FT_TT_PROC_COUNT],
|
|
const dram_region *region)
|
|
{
|
|
ft_tt_launch_config config;
|
|
uint64_t cb_mask = 0;
|
|
unsigned bad_tile = 0;
|
|
unsigned bad_element = 0;
|
|
int rc;
|
|
|
|
ft_tt_test_step("stage A — reader pulls both operands from DRAM bank %u at translated "
|
|
"(%u, %u); writer stays in this core's L1",
|
|
DRAM_LOGICAL_BANK, (unsigned)region->coord.x, (unsigned)region->coord.y);
|
|
|
|
rc = write_cb_config(noc, x, y, &cb_mask);
|
|
ft_tt_test_check_fmt("CB config written", rc == FT_TT_OK, "mask=0x%llx",
|
|
(unsigned long long)cb_mask);
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
build_config(placements, cb_mask, &config);
|
|
rc = plant_runtime_args(noc, x, y, &config, region, x, y, g_layout.destination_address);
|
|
ft_tt_test_check_fmt("runtime args planted", rc == FT_TT_OK,
|
|
"reader src DRAM 0x%llx / 0x%llx, writer dst L1 (%u,%u) 0x%05x",
|
|
(unsigned long long)region->source0.address,
|
|
(unsigned long long)region->source1.address, x, y,
|
|
g_layout.destination_address);
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
rc = stage_dram_sources(path, region, 0u);
|
|
ft_tt_test_check_fmt("source tiles written to DRAM", rc == FT_TT_OK, "%u tiles x %u B",
|
|
TILE_COUNT, FT_TT_PHASE2_TILE_BYTES);
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
rc = verify_dram_sources(path, region, 0u);
|
|
ft_tt_test_check("the host reads back what it wrote to DRAM", rc == FT_TT_OK,
|
|
"so a later mismatch is the kernel's, not the staging's");
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
rc = wipe_l1_destination(noc, x, y);
|
|
if (rc == FT_TT_OK)
|
|
rc = launch_and_wait(noc, x, y, &config);
|
|
ft_tt_test_check_fmt("the triad completed with a DRAM source", rc == FT_TT_OK, "rc=%d (%s)",
|
|
rc, ft_tt_error_string(rc));
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
rc = check_l1_destination(noc, x, y, 0u, &bad_tile, &bad_element);
|
|
ft_tt_test_check_fmt("the L1 result is byte-exact against the C reference", rc == FT_TT_OK,
|
|
"%u tiles x %u elements; matched up to tile %u element %u", TILE_COUNT,
|
|
TILE_ELEMENTS, bad_tile, bad_element);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* Stage B — both directions cross the fabric */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/** One full DRAM-to-DRAM run at input pass `pass`. Returns FT_TT_OK on a byte-exact
|
|
* result, and reports the first difference otherwise. */
|
|
static int run_dram_pass(const ft_tt_noc_access *noc, ft_tt_noc_path *path, uint16_t x, uint16_t y,
|
|
const ft_tt_launch_config *config, const dram_region *region,
|
|
unsigned pass, unsigned *out_bad_tile, unsigned *out_bad_element)
|
|
{
|
|
int rc = stage_dram_sources(path, region, pass);
|
|
|
|
if (rc == FT_TT_OK)
|
|
rc = poison_dram_destination(path, region);
|
|
if (rc == FT_TT_OK)
|
|
rc = launch_and_wait(noc, x, y, config);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
return check_dram_destination(path, region, pass, out_bad_tile, out_bad_element);
|
|
}
|
|
|
|
static void run_stage_b_dram_to_dram(const ft_tt_noc_access *noc, ft_tt_noc_path *path, uint16_t x,
|
|
uint16_t y,
|
|
const ft_tt_image_placement placements[FT_TT_PROC_COUNT],
|
|
const dram_region *region)
|
|
{
|
|
ft_tt_launch_config config;
|
|
uint64_t cb_mask = 0;
|
|
unsigned bad_tile = 0;
|
|
unsigned bad_element = 0;
|
|
unsigned completed = 0;
|
|
unsigned pass;
|
|
int rc;
|
|
|
|
ft_tt_test_step("stage B — DRAM in, DRAM out: the writer crosses the fabric too, on NoC 1");
|
|
|
|
rc = write_cb_config(noc, x, y, &cb_mask);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_test_check("CB config written", false, ft_tt_error_string(rc));
|
|
return;
|
|
}
|
|
build_config(placements, cb_mask, &config);
|
|
rc = plant_runtime_args(noc, x, y, &config, region, region->coord.x, region->coord.y,
|
|
(uint32_t)region->destination.address);
|
|
ft_tt_test_check_fmt("runtime args planted", rc == FT_TT_OK,
|
|
"writer dst DRAM (%u,%u) 0x%llx", (unsigned)region->coord.x,
|
|
(unsigned)region->coord.y,
|
|
(unsigned long long)region->destination.address);
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
rc = run_dram_pass(noc, path, x, y, &config, region, 0u, &bad_tile, &bad_element);
|
|
ft_tt_test_check_fmt("DRAM to DRAM is byte-exact against the C reference", rc == FT_TT_OK,
|
|
"%u tiles x %u elements; matched up to tile %u element %u", TILE_COUNT,
|
|
TILE_ELEMENTS, bad_tile, bad_element);
|
|
if (rc != FT_TT_OK)
|
|
return;
|
|
|
|
/* Different DRAM contents, same everything else. If the output tracks the input the
|
|
* reader really read DRAM; if it does not, it read something stale. */
|
|
ft_tt_test_step("stage B — the result must TRACK the DRAM contents, including zeros");
|
|
rc = run_dram_pass(noc, path, x, y, &config, region, 7u, &bad_tile, &bad_element);
|
|
ft_tt_test_check_fmt("a second, different DRAM input gives its own answer", rc == FT_TT_OK,
|
|
"pass 7; first difference tile %u element %u", bad_tile, bad_element);
|
|
|
|
rc = run_dram_pass(noc, path, x, y, &config, region, PASS_ZEROED, &bad_tile, &bad_element);
|
|
ft_tt_test_check_fmt("a ZEROED DRAM source produces a zeroed result", rc == FT_TT_OK,
|
|
"the destination held 0xA5 before the run, so this is not staleness; "
|
|
"first difference tile %u element %u",
|
|
bad_tile, bad_element);
|
|
|
|
ft_tt_test_step("stage B — %u repeats, destination poisoned and checked every time",
|
|
REPEAT_COUNT);
|
|
for (pass = 0; pass < REPEAT_COUNT; pass++) {
|
|
rc = run_dram_pass(noc, path, x, y, &config, region, pass % 13u, &bad_tile, &bad_element);
|
|
if (rc != FT_TT_OK)
|
|
break;
|
|
completed++;
|
|
}
|
|
ft_tt_test_check_fmt("every repeat was byte-exact", completed == REPEAT_COUNT,
|
|
"%u/%u, last rc=%d, first difference tile %u element %u", completed,
|
|
REPEAT_COUNT, rc, bad_tile, bad_element);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/** Claim the three DRAM buffers and the endpoint that serves them. */
|
|
static int claim_dram_region(ft_tt_allocator *allocator, const ft_tt_grid_info *grid,
|
|
dram_region *out_region)
|
|
{
|
|
int rc;
|
|
|
|
memset(out_region, 0, sizeof(*out_region));
|
|
rc = ft_tt_coord_dram_bank(grid, DRAM_LOGICAL_BANK, FT_TT_COORD_TRANSLATED,
|
|
&out_region->coord);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
rc = ft_tt_buffer_alloc(allocator, FT_TT_MEM_SPACE_DRAM, DRAM_LOGICAL_BANK, BUFFER_BYTES,
|
|
&out_region->source0);
|
|
if (rc == FT_TT_OK)
|
|
rc = ft_tt_buffer_alloc(allocator, FT_TT_MEM_SPACE_DRAM, DRAM_LOGICAL_BANK, BUFFER_BYTES,
|
|
&out_region->source1);
|
|
if (rc == FT_TT_OK)
|
|
rc = ft_tt_buffer_alloc(allocator, FT_TT_MEM_SPACE_DRAM, DRAM_LOGICAL_BANK, BUFFER_BYTES,
|
|
&out_region->destination);
|
|
if (rc != FT_TT_OK)
|
|
return rc;
|
|
|
|
/* The kernels take a 32-bit base address as a runtime arg and build the NoC address
|
|
* themselves, so a buffer above 4 GiB in a bank would be truncated silently. */
|
|
if ((out_region->source0.address | out_region->source1.address |
|
|
out_region->destination.address) > 0xFFFFFFFFu) {
|
|
ft_tt_log_error("dram: a buffer landed above 4 GiB, which the kernels' 32-bit "
|
|
"address argument cannot express");
|
|
return FT_TT_EINVAL;
|
|
}
|
|
return FT_TT_OK;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
ft_tt_hardware_session session;
|
|
ft_tt_firmware_set firmware;
|
|
ft_tt_program_directory program;
|
|
ft_tt_allocator allocator;
|
|
ft_tt_noc_path dram_path;
|
|
ft_tt_image_placement placements[FT_TT_PROC_COUNT];
|
|
dram_region region;
|
|
ft_tt_ctx *kmd;
|
|
const char *directory;
|
|
bool path_open = false;
|
|
uint16_t x = 0;
|
|
uint16_t y = 0;
|
|
int rc;
|
|
|
|
if (argc < 2) {
|
|
fprintf(stderr, "usage: %s <program-directory>\n", argv[0]);
|
|
return 2;
|
|
}
|
|
directory = argv[1];
|
|
|
|
rc = ft_tt_hardware_session_open(&session);
|
|
if (rc != FT_TT_OK)
|
|
return 1;
|
|
rc = ft_tt_hardware_session_tensix_from_environment(&session, &x, &y, NULL);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_hardware_session_close(&session);
|
|
return 1;
|
|
}
|
|
|
|
/* This arm allocates DRAM and opens its own window through the TT-KMD CONTEXT,
|
|
* so it is one of the two harnesses that cannot run on the userspace driver.
|
|
* It says which transport it needs rather than dereferencing a handle that is
|
|
* NULL there. */
|
|
kmd = ft_tt_hardware_session_kmd_context(&session);
|
|
if (!kmd) {
|
|
ft_tt_log_error("this suite allocates DRAM through the tt-kmd context "
|
|
"(ft_tt_allocator_init); run it with FT_TT_DRIVER=kmd");
|
|
ft_tt_hardware_session_close(&session);
|
|
return 1;
|
|
}
|
|
|
|
/* A SECOND window: ft_tt_noc_path must be the only thing aiming the window it is
|
|
* attached to, and the session's window belongs to the launch accessor. */
|
|
rc = ft_tt_noc_path_open(kmd, FT_TT_KMD_CACHE_WC, &dram_path);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_hardware_session_close(&session);
|
|
return 1;
|
|
}
|
|
path_open = true;
|
|
|
|
rc = ft_tt_allocator_init(&allocator, kmd, &session.grid, 0u);
|
|
if (rc == FT_TT_OK)
|
|
rc = claim_dram_region(&allocator, &session.grid, ®ion);
|
|
if (rc != FT_TT_OK) {
|
|
(void)ft_tt_noc_path_close(&dram_path);
|
|
ft_tt_hardware_session_close(&session);
|
|
return 1;
|
|
}
|
|
printf("target Tensix 0 at translated (%u, %u); DRAM bank %u at translated (%u, %u)\n",
|
|
(unsigned)x, (unsigned)y, DRAM_LOGICAL_BANK, (unsigned)region.coord.x,
|
|
(unsigned)region.coord.y);
|
|
|
|
rc = ft_tt_firmware_set_load(directory, &firmware);
|
|
if (rc != FT_TT_OK)
|
|
goto close_path;
|
|
rc = ft_tt_program_directory_load(directory, 0x1fu, &program);
|
|
if (rc != FT_TT_OK) {
|
|
ft_tt_firmware_set_release(&firmware);
|
|
goto close_path;
|
|
}
|
|
|
|
ft_tt_test_step("setup — boot the firmware and load the vector_add triad from %s", directory);
|
|
rc = boot_and_derive_layout(&session.noc, x, y, &firmware);
|
|
ft_tt_test_check_fmt("firmware booted", rc == FT_TT_OK, "rc=%d (%s)", rc,
|
|
ft_tt_error_string(rc));
|
|
if (rc != FT_TT_OK)
|
|
goto done;
|
|
|
|
rc = ft_tt_core_load_program(&session.noc, x, y, &program.program, placements);
|
|
ft_tt_test_check_fmt("all five images loaded", rc == FT_TT_OK, "%u images",
|
|
program.program.image_count);
|
|
if (rc != FT_TT_OK)
|
|
goto done;
|
|
|
|
run_stage_a_dram_to_l1(&session.noc, &dram_path, x, y, placements, ®ion);
|
|
if (ft_tt_test_failure_count() != 0)
|
|
goto done;
|
|
|
|
run_stage_b_dram_to_dram(&session.noc, &dram_path, x, y, placements, ®ion);
|
|
|
|
done:
|
|
/* Assert reset on every exit path: a core left running can still issue NoC
|
|
* transactions, and this is the first program whose transactions leave the tile. */
|
|
(void)ft_tt_core_park(&session.noc, x, y);
|
|
ft_tt_program_directory_release(&program);
|
|
ft_tt_firmware_set_release(&firmware);
|
|
close_path:
|
|
if (path_open)
|
|
(void)ft_tt_noc_path_close(&dram_path);
|
|
ft_tt_hardware_session_close(&session);
|
|
return ft_tt_test_finish();
|
|
}
|