115 lines
3.5 KiB
C
115 lines
3.5 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/**
|
|
* test_read_width.c — is the card→host read rate a latency ceiling or a
|
|
* transaction-count effect?
|
|
*
|
|
* spec §9.8 currently says 2.3 MB/s is "a ceiling, not a tuning target", on the
|
|
* reasoning that every uncached read is a serialised non-posted PCIe round-trip.
|
|
* If that is the whole story, widening the load should change nothing. If the
|
|
* rate instead tracks the number of load instructions, it should scale with load
|
|
* width.
|
|
*
|
|
* Same buffer, same window, same aim, back to back — only the load width differs.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 199309L
|
|
|
|
#include "ft_tt_arc_telemetry.h"
|
|
#include "ft_tt_coord.h"
|
|
#include "ft_tt_device.h"
|
|
#include "ft_tt_tlb.h"
|
|
|
|
#include <emmintrin.h> /* SSE2 only — this host has no AVX */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define WINDOW_BYTES (2u * 1024u * 1024u)
|
|
#define DRAM_AIM_BASE 0x200000u
|
|
#define TRANSFER_BYTES WINDOW_BYTES
|
|
#define REPEATS 4u
|
|
|
|
static double now_seconds(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
|
}
|
|
|
|
static void read_8b(unsigned char *dst, volatile const unsigned char *src, size_t bytes)
|
|
{
|
|
uint64_t *d = (uint64_t *)(void *)dst;
|
|
volatile const uint64_t *s = (volatile const uint64_t *)(const void *)src;
|
|
for (size_t i = 0; i < bytes / 8u; i++) {
|
|
d[i] = s[i];
|
|
}
|
|
}
|
|
|
|
static void read_16b(unsigned char *dst, volatile const unsigned char *src, size_t bytes)
|
|
{
|
|
for (size_t off = 0; off < bytes; off += 16u) {
|
|
__m128i v = _mm_loadu_si128((const __m128i *)(const void *)(src + off));
|
|
_mm_storeu_si128((__m128i *)(void *)(dst + off), v);
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
ft_tt_ctx *ctx = ft_tt_init_device(0);
|
|
if (ctx == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
ft_tt_grid_info grid;
|
|
ft_tt_noc_coord dram;
|
|
if (ft_tt_arc_telemetry_read_grid(ctx, &grid) != FT_TT_OK ||
|
|
ft_tt_coord_check_grid(&grid) != FT_TT_OK ||
|
|
ft_tt_coord_dram_bank(&grid, 0, FT_TT_COORD_TRANSLATED, &dram) != FT_TT_OK) {
|
|
ft_tt_ctx_free(ctx);
|
|
return 1;
|
|
}
|
|
|
|
ft_tt_tlb_window *w = NULL;
|
|
if (ft_tt_tlb_window_alloc(ctx, WINDOW_BYTES, FT_TT_KMD_CACHE_WC, &w) != FT_TT_OK) {
|
|
ft_tt_ctx_free(ctx);
|
|
return 1;
|
|
}
|
|
if (ft_tt_tlb_window_aim(ctx, w, (uint16_t)dram.x, (uint16_t)dram.y, DRAM_AIM_BASE) !=
|
|
FT_TT_OK) {
|
|
ft_tt_ctx_free(ctx);
|
|
return 1;
|
|
}
|
|
|
|
volatile unsigned char *ap = ft_tt_tlb_window_aperture(w);
|
|
unsigned char *buf = malloc(TRANSFER_BYTES);
|
|
double mb = (double)TRANSFER_BYTES * REPEATS / (1024.0 * 1024.0);
|
|
|
|
printf("DRAM bank 0 translated (%u, %u), WC, %u MiB x %u, reads only\n\n", dram.x, dram.y,
|
|
TRANSFER_BYTES / (1024u * 1024u), REPEATS);
|
|
|
|
double t0 = now_seconds();
|
|
for (uint32_t r = 0; r < REPEATS; r++) {
|
|
read_8b(buf, ap, TRANSFER_BYTES);
|
|
}
|
|
double s8 = now_seconds() - t0;
|
|
|
|
t0 = now_seconds();
|
|
for (uint32_t r = 0; r < REPEATS; r++) {
|
|
read_16b(buf, ap, TRANSFER_BYTES);
|
|
}
|
|
double s16 = now_seconds() - t0;
|
|
|
|
printf(" 8 B loads %6.2f MB/s\n", mb / s8);
|
|
printf(" 16 B loads %6.2f MB/s\n", mb / s16);
|
|
printf("\n ratio %.2fx — %s\n", s8 / s16,
|
|
(s8 / s16) > 1.5 ? "scales with load width: transaction-bound, NOT a latency ceiling"
|
|
: "flat: consistent with a latency ceiling");
|
|
|
|
free(buf);
|
|
(void)ft_tt_tlb_window_free(ctx, w);
|
|
ft_tt_ctx_free(ctx);
|
|
return 0;
|
|
}
|