673 lines
28 KiB
C
673 lines
28 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file resident_puller.c
|
|
* @brief STEP 3 (A) — the ON-CARD PULLER: a resident BRISC kernel that replays a recorded
|
|
* decode token's queue traffic (parameter blocks, ring slots, head bumps, awaits) into
|
|
* the other residencies' queues over the NoC, one SEGMENT per descriptor.
|
|
*
|
|
* @status A2: dry-run (log every word it would write) and live mode.
|
|
* @issues None
|
|
* @todo B: identical member ring words + multicast fan-out.
|
|
*
|
|
* THE PROGRAM (in this core's L1 at descriptor a0; built by Pantheon pn_tt_dispatch.c):
|
|
* header, 16 words: 0 magic 'DPLR' 1 version 2 queues 3 queue table offset (bytes)
|
|
* 4 segments 5 segment table offset 6 log address (L1) 7 log capacity (bytes)
|
|
* 8 OUT error 9 OUT failing entry offset 10 OUT log bytes 11 OUT last await count
|
|
* 12 OUT cycles >> 4 inside PULL descriptors (cumulative) 13 OUT of those, in AWAIT polls
|
|
* 14 multicast groups 15 group table offset (bytes)
|
|
* queue table, 4 words a queue: x | y << 16, nonce, mask (capacity-1), 0
|
|
* group table (STEP 3 B), 8 words a group: representative queue (its nonce/mask stand for
|
|
* all), x_lo, y_lo, x_hi, y_hi, destinations (<= 4: the recorded wedge rule), 0, 0.
|
|
* A RING or HEAD entry whose queue word has bit 31 set names GROUP (queue & 0xFFFF): the
|
|
* slot is MULTICAST to the rectangle (every member's ring words are identical: shared
|
|
* nonce, same index, same parameter-cache slot), the head by a 4-byte multicast write.
|
|
* segment table: one byte offset a segment
|
|
* entries, word 0 = type | count << 8 | patches << 20:
|
|
* 1 WRITE xy, addr, words[count], patches x {word, stride}
|
|
* 2 RING queue, index0, step, words[8*count] (word 1 of each slot = the token, computed
|
|
* here), patches x {word, stride}
|
|
* 3 HEAD queue, base, stride
|
|
* 4 AWAIT queue, count0, step
|
|
* 5 SEGMENT END 6 PROGRAM END
|
|
* 7 ROWCOPY the 9 ROWCOPY parameter words, run inline (S2d streamed segments)
|
|
* 8 WRITE DIRECT xy, addr, `pad` words (head bits 20-21), words[count]: the payload is
|
|
* 16 B-aligned like its destination, so the NoC writes it straight from the
|
|
* program (no copy through scratch). Lands by the next barrier like any write.
|
|
* Cumulative cycles >> 4 also in control words 62 (inside PULL) and 63 (in AWAIT polls),
|
|
* and PER QUEUE at PULL_AWAIT_TABLE (cycles >> 4, awaits) — the AWAIT census (PM 8).
|
|
* Every per-token word is `base + stride * k` (k = a1); the ring index is `index0 + step*k`.
|
|
*
|
|
* THE DESCRIPTOR: opcode 0x14 PULL; a0 points at its parameter block (the host's ordinary
|
|
* residency enqueue): 0 = program, 1 = k, 2 = first segment, 3 = last segment (inclusive),
|
|
* 4 bit 0 = DRY RUN (log every word, write nothing to any other core); REDS: bit 1 PARAMETER-
|
|
* block patches (KV row, positions) are frozen at the model token (k = 0) — ring words,
|
|
* indices and tokens stay right (k+1 was argmax-lucky IDENTICAL: one zero KV row), bit 2 the first ring slot's token is flipped, bit 3
|
|
* every AWAIT is skipped, bit 5 the first MULTICAST ring slot skips its last member.
|
|
*
|
|
* ORDERING. A target's ring slot and parameter block must LAND before its head says so: heads
|
|
* are queued, and every queued head is written only after a NoC write barrier (one barrier for
|
|
* up to 32 heads). An AWAIT, a segment end or a full head list flushes first. Awaits are a
|
|
* 16-byte read of the target's words 4..7: count >= wanted, then the TOKEN of the last
|
|
* completed descriptor and the state are checked, exactly as the host checks them.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "../fw/ft_bh_regs.h"
|
|
#include "ft_tensix_dataflow.h"
|
|
#include "ft_tensix_noc.h"
|
|
#include "ft_tensix_noc_multicast.h"
|
|
#include "resident_queue_protocol.h"
|
|
|
|
enum { kOpPull = 0x14 };
|
|
/* STEP 4 S2: data movers for the speculative VERIFY (host param block at a0):
|
|
* ROWCOPY 0x15 {src_x, src_y, src_addr, src_row, dst_x, dst_y, dst_addr, dst_row, tiles}:
|
|
* row `src_row` of every bf16 tile of a DRAM strip -> row `dst_row` of another (two 32 B
|
|
* face rows a tile; the source read is the 64 B-aligned pair, the DRAM read rule). */
|
|
enum { kOpRowCopy = 0x15 };
|
|
/* FILL 0x16 {dst_x, dst_y, dst_addr, bytes, value}: every 32-bit word of DRAM
|
|
* [dst_addr, dst_addr + bytes) becomes `value` — the carried GDN state's zero at a sequence
|
|
* start, on card instead of ~120 MB over the link. dst_addr and bytes 32 B multiples (the
|
|
* NoC's write rule is 16 B; the host asks 64). Source: 32 KB of scratch written once. */
|
|
enum { kOpFill = 0x16 };
|
|
/* COPY 0x17 {src_x, src_y, src_addr, dst_x, dst_y, dst_addr, bytes}: DRAM [src_addr, +bytes)
|
|
* -> [dst_addr, +bytes), card side, through the scratch in two halves (the read of the next
|
|
* half overlaps the write of this one). Addresses and bytes 64 B multiples (the DRAM read rule).
|
|
* The multi-turn checkpoint of the carried GDN state: ~120 MB that the link reads at 5 MB/s. */
|
|
enum { kOpCopy = 0x17 };
|
|
|
|
enum {
|
|
kPlrMagic = 0x524C5044u, /* "DPLR" */
|
|
kPlrEntryWrite = 1u,
|
|
kPlrEntryRing = 2u,
|
|
kPlrEntryHead = 3u,
|
|
kPlrEntryAwait = 4u,
|
|
kPlrEntrySegment = 5u,
|
|
kPlrEntryEnd = 6u,
|
|
kPlrEntryRowCopy = 7u, /* S2d: an inline ROWCOPY, its 9 parameter words follow */
|
|
kPlrEntryWriteDirect = 8u /* S2e: WRITE from the program itself (pad words to 16 B) */
|
|
};
|
|
|
|
enum {
|
|
kPlrErrNone = 0u,
|
|
kPlrErrMagic = 1u,
|
|
kPlrErrEntry = 2u,
|
|
kPlrErrAwaitTimeout = 3u,
|
|
kPlrErrAwaitToken = 4u,
|
|
kPlrErrAwaitPoison = 5u,
|
|
kPlrErrLogFull = 6u,
|
|
kPlrErrInline = 7u,
|
|
kPlrErrSegment = 8u
|
|
};
|
|
|
|
#define PULL_QUEUE_CONTROL 0x170000u
|
|
#define PULL_QUEUE_RING 0x170100u
|
|
#define PULL_HEAD_OFFSET 8u
|
|
#define PULL_WINDOW_OFFSET 16u /* words 4..7: stop, count, token, state */
|
|
#define PULL_TOKEN_MULT 0x9E3779B9u
|
|
#define PULL_STATE_POISON 0x7B1B1B1Bu
|
|
#define PULL_MAX_HEADS 32u
|
|
#define PULL_SCRATCH_BYTES 0xC000u /* 48 KB of write sources */
|
|
#define PULL_AWAIT_BOUND 200000000u
|
|
|
|
/* Scratch: write sources, the poll buffer and the pending heads. Fixed L1, below the queue
|
|
* region and above the program arena's end (the host keeps the program + log under it). */
|
|
#define PULL_SCRATCH_BASE 0x160000u
|
|
#define PULL_POLL_BUFFER (PULL_SCRATCH_BASE + PULL_SCRATCH_BYTES)
|
|
/* PM 8: the AWAIT census — per queue-table index, two words: cumulative cycles >> 4 spent
|
|
* polling that queue, and the number of AWAITs on it. Never reset here (the host diffs two
|
|
* reads), so it needs no initialisation. 256 queues x 8 B, clear of the poll buffer. */
|
|
#define PULL_AWAIT_TABLE (PULL_SCRATCH_BASE + PULL_SCRATCH_BYTES + 0x1000u)
|
|
#define PULL_AWAIT_QUEUES 256u
|
|
|
|
typedef struct {
|
|
volatile uint32_t *header;
|
|
const volatile uint32_t *queues;
|
|
uint32_t k;
|
|
uint32_t patch_k;
|
|
bool dry;
|
|
bool red_bad_token;
|
|
bool red_no_wait;
|
|
bool red_group_token;
|
|
uint32_t scratch_used;
|
|
bool direct_pending;
|
|
uint32_t heads;
|
|
uint32_t head_xy[PULL_MAX_HEADS];
|
|
uint32_t head_value[PULL_MAX_HEADS];
|
|
const volatile uint32_t *groups;
|
|
volatile uint32_t *log;
|
|
uint32_t log_words, log_capacity_words;
|
|
} pull_state;
|
|
|
|
static uint32_t pull_token(uint32_t nonce, uint32_t index)
|
|
{
|
|
uint32_t token = nonce ^ ((index + 1u) * PULL_TOKEN_MULT);
|
|
|
|
if (token == 0u || token == PULL_STATE_POISON)
|
|
token = nonce ^ 0xA5A5A5A5u;
|
|
return token;
|
|
}
|
|
|
|
static bool pull_log(pull_state *s, uint32_t type, uint32_t xy, uint32_t addr,
|
|
const volatile uint32_t *words, uint32_t count)
|
|
{
|
|
uint32_t i;
|
|
|
|
if (s->log_words + 4u + count > s->log_capacity_words)
|
|
return false;
|
|
s->log[s->log_words++] = type;
|
|
s->log[s->log_words++] = xy;
|
|
s->log[s->log_words++] = addr;
|
|
s->log[s->log_words++] = count;
|
|
for (i = 0; i < count; i++)
|
|
s->log[s->log_words++] = words[i];
|
|
return true;
|
|
}
|
|
|
|
static volatile uint32_t *pull_scratch(pull_state *s, uint32_t bytes);
|
|
|
|
/** Write every queued head, after one barrier that lands every ring slot and block before. */
|
|
static bool pull_flush_heads(pull_state *s)
|
|
{
|
|
uint32_t i;
|
|
|
|
if (s->heads == 0u)
|
|
return true;
|
|
if (!s->dry)
|
|
ft_tensix_noc_write_barrier();
|
|
for (i = 0; i < s->heads; i++) {
|
|
const uint32_t xy = s->head_xy[i];
|
|
|
|
if (s->dry) {
|
|
volatile uint32_t one[1];
|
|
|
|
one[0] = s->head_value[i];
|
|
if (!pull_log(s, kPlrEntryHead, xy, PULL_QUEUE_CONTROL + PULL_HEAD_OFFSET, one, 1u))
|
|
return false;
|
|
continue;
|
|
}
|
|
if ((xy & 0x80000000u) != 0u) {
|
|
const volatile uint32_t *g = s->groups + 8u * (xy & 0xFFFFu);
|
|
volatile uint32_t *line = pull_scratch(s, 16u);
|
|
uint64_t target;
|
|
|
|
/* The source word sits at the destination's offset within its 16-byte line, as
|
|
* for the unicast head: an L1 write lands lane for lane. */
|
|
line[PULL_HEAD_OFFSET / 4u] = s->head_value[i];
|
|
if (ft_tensix_multicast_address(g[1], g[2], g[3], g[4],
|
|
PULL_QUEUE_CONTROL + PULL_HEAD_OFFSET,
|
|
&target) != FT_TENSIX_MULTICAST_OK ||
|
|
ft_tensix_noc_semaphore_set_multicast((uint32_t)(uintptr_t)line + PULL_HEAD_OFFSET,
|
|
target, g[5]) != FT_TENSIX_MULTICAST_OK)
|
|
return false;
|
|
continue;
|
|
}
|
|
{
|
|
/* A PLAIN 4-byte NoC write, never the inline dword write: thousands of inline
|
|
* writes from this BRISC wedged its NEXT firmware boot every time (instrn_buf
|
|
* 0x66, cores 89 and 70; run_d_wedge4.sh: 3/3 clean with plain writes, the inline
|
|
* control wedged). The source word sits at the destination's offset in a line. */
|
|
volatile uint32_t *line = pull_scratch(s, 16u);
|
|
|
|
line[PULL_HEAD_OFFSET / 4u] = s->head_value[i];
|
|
ft_tensix_noc_write((uint32_t)(uintptr_t)line + PULL_HEAD_OFFSET,
|
|
ft_tensix_noc_address(xy & 0xFFFFu, xy >> 16,
|
|
PULL_QUEUE_CONTROL + PULL_HEAD_OFFSET),
|
|
4u);
|
|
}
|
|
}
|
|
s->heads = 0u;
|
|
if (!s->dry)
|
|
ft_tensix_noc_write_barrier();
|
|
s->scratch_used = 0u;
|
|
return true;
|
|
}
|
|
|
|
/** Source bytes for one write: a slice of scratch, recycled only after a barrier. */
|
|
static volatile uint32_t *pull_scratch(pull_state *s, uint32_t bytes)
|
|
{
|
|
volatile uint32_t *at;
|
|
|
|
bytes = (bytes + 15u) & ~15u;
|
|
if (s->scratch_used + bytes > PULL_SCRATCH_BYTES) {
|
|
if (!s->dry)
|
|
ft_tensix_noc_write_barrier();
|
|
s->scratch_used = 0u;
|
|
}
|
|
at = (volatile uint32_t *)(uintptr_t)(PULL_SCRATCH_BASE + s->scratch_used);
|
|
s->scratch_used += bytes;
|
|
return at;
|
|
}
|
|
|
|
static void pull_apply_patches(volatile uint32_t *words, const volatile uint32_t *patch,
|
|
uint32_t patches, uint32_t k)
|
|
{
|
|
uint32_t p;
|
|
|
|
for (p = 0; p < patches; p++)
|
|
words[patch[2u * p]] += patch[2u * p + 1u] * k;
|
|
}
|
|
|
|
static bool pull_row_copy(const volatile uint32_t *a);
|
|
|
|
/** Run entries from `entry` until a segment end. @return 0 or an error code. */
|
|
static uint32_t pull_segment(pull_state *s, const volatile uint32_t *entry,
|
|
volatile uint32_t *control)
|
|
{
|
|
for (;;) {
|
|
const uint32_t head = entry[0];
|
|
const uint32_t type = head & 0xFFu;
|
|
const uint32_t count = (head >> 8) & 0xFFFu;
|
|
const uint32_t patches = head >> 20;
|
|
|
|
s->header[9] = (uint32_t)((uintptr_t)entry - (uintptr_t)s->header);
|
|
if (control[kFieldStopRequest] != 0u)
|
|
return kPlrErrSegment;
|
|
switch (type) {
|
|
case kPlrEntryWrite: {
|
|
const uint32_t xy = entry[1], addr = entry[2];
|
|
volatile uint32_t *words = pull_scratch(s, count * 4u);
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
words[i] = entry[3u + i];
|
|
pull_apply_patches(words, entry + 3u + count, patches, s->patch_k);
|
|
if (s->dry) {
|
|
if (!pull_log(s, kPlrEntryWrite, xy, addr, words, count))
|
|
return kPlrErrLogFull;
|
|
} else {
|
|
ft_tensix_noc_write((uint32_t)(uintptr_t)words,
|
|
ft_tensix_noc_address(xy & 0xFFFFu, xy >> 16, addr),
|
|
count * 4u);
|
|
}
|
|
entry += 3u + count + 2u * patches;
|
|
break;
|
|
}
|
|
case kPlrEntryRing: {
|
|
const bool group = (entry[1] & 0x80000000u) != 0u;
|
|
const volatile uint32_t *g = s->groups + 8u * (entry[1] & 0xFFFFu);
|
|
const uint32_t q = group ? g[0] : entry[1];
|
|
const uint32_t xy = group ? entry[1] : s->queues[4u * q];
|
|
const uint32_t nonce = s->queues[4u * q + 1u];
|
|
const uint32_t mask = s->queues[4u * q + 2u];
|
|
const uint32_t index = entry[2] + entry[3] * s->k;
|
|
const uint32_t words_n = 8u * count;
|
|
const uint32_t addr = PULL_QUEUE_RING + (index & mask) * 32u;
|
|
volatile uint32_t *words = pull_scratch(s, words_n * 4u);
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < words_n; i++)
|
|
words[i] = entry[4u + i];
|
|
pull_apply_patches(words, entry + 4u + words_n, patches, s->k);
|
|
for (i = 0; i < count; i++)
|
|
words[8u * i + 1u] = pull_token(nonce, index + i);
|
|
if (s->red_bad_token) {
|
|
words[1] ^= 1u;
|
|
s->red_bad_token = false;
|
|
}
|
|
if (s->dry) {
|
|
if (!pull_log(s, kPlrEntryRing, xy, addr, words, words_n))
|
|
return kPlrErrLogFull;
|
|
} else if (group) {
|
|
uint64_t target;
|
|
/* RED (bit 5): the first group slot misses its last member (x_hi - 1) — that
|
|
* member never sees the descriptor, member 0's pull waits on it: must REFUSE. */
|
|
const bool shrink = s->red_group_token && g[5] >= 2u;
|
|
const uint32_t x_hi = shrink ? g[3] - 1u : g[3];
|
|
|
|
if (shrink)
|
|
s->red_group_token = false;
|
|
if (ft_tensix_multicast_address(g[1], g[2], x_hi, g[4], addr, &target) !=
|
|
FT_TENSIX_MULTICAST_OK ||
|
|
ft_tensix_noc_write_multicast((uint32_t)(uintptr_t)words, target,
|
|
words_n * 4u, shrink ? g[5] - 1u : g[5]) !=
|
|
FT_TENSIX_MULTICAST_OK)
|
|
return kPlrErrEntry;
|
|
} else {
|
|
ft_tensix_noc_write((uint32_t)(uintptr_t)words,
|
|
ft_tensix_noc_address(xy & 0xFFFFu, xy >> 16, addr),
|
|
words_n * 4u);
|
|
}
|
|
entry += 4u + words_n + 2u * patches;
|
|
break;
|
|
}
|
|
case kPlrEntryHead:
|
|
if (s->heads == PULL_MAX_HEADS && !pull_flush_heads(s))
|
|
return kPlrErrInline;
|
|
s->head_xy[s->heads] = (entry[1] & 0x80000000u) != 0u ? entry[1]
|
|
: s->queues[4u * entry[1]];
|
|
s->head_value[s->heads] = entry[2] + entry[3] * s->k;
|
|
s->heads++;
|
|
entry += 4u;
|
|
break;
|
|
case kPlrEntryAwait: {
|
|
const uint32_t q = entry[1];
|
|
const uint32_t xy = s->queues[4u * q];
|
|
const uint32_t nonce = s->queues[4u * q + 1u];
|
|
const uint32_t wanted = entry[2] + entry[3] * s->k;
|
|
volatile uint32_t *poll = (volatile uint32_t *)(uintptr_t)PULL_POLL_BUFFER;
|
|
uint32_t spins = 0u;
|
|
|
|
if (!pull_flush_heads(s))
|
|
return kPlrErrInline;
|
|
if (s->red_no_wait) {
|
|
entry += 4u;
|
|
break;
|
|
}
|
|
if (s->dry) {
|
|
volatile uint32_t one[1];
|
|
|
|
one[0] = wanted;
|
|
if (!pull_log(s, kPlrEntryAwait, xy, PULL_QUEUE_CONTROL + 0x14u, one, 1u))
|
|
return kPlrErrLogFull;
|
|
entry += 4u;
|
|
break;
|
|
}
|
|
const uint32_t await_from = ft_bh_wall_clock_low();
|
|
|
|
for (;;) {
|
|
ft_tensix_noc_read(ft_tensix_noc_address(xy & 0xFFFFu, xy >> 16,
|
|
PULL_QUEUE_CONTROL + PULL_WINDOW_OFFSET),
|
|
PULL_POLL_BUFFER, 16u);
|
|
ft_tensix_noc_read_barrier();
|
|
ft_bh_l1_cache_invalidate();
|
|
if (poll[1] >= wanted)
|
|
break;
|
|
if (control[kFieldStopRequest] != 0u || ++spins >= PULL_AWAIT_BOUND) {
|
|
s->header[11] = poll[1];
|
|
return kPlrErrAwaitTimeout;
|
|
}
|
|
}
|
|
s->header[11] = poll[1];
|
|
{
|
|
const uint32_t waited = (ft_bh_wall_clock_low() - await_from) >> 4;
|
|
|
|
s->header[13] += waited;
|
|
control[63] += waited;
|
|
if (q < PULL_AWAIT_QUEUES) {
|
|
volatile uint32_t *census =
|
|
(volatile uint32_t *)(uintptr_t)PULL_AWAIT_TABLE + 2u * q;
|
|
|
|
census[0] += waited;
|
|
census[1] += 1u;
|
|
}
|
|
}
|
|
if (poll[3] == PULL_STATE_POISON)
|
|
return kPlrErrAwaitPoison;
|
|
if (poll[2] != pull_token(nonce, wanted - 1u))
|
|
return kPlrErrAwaitToken;
|
|
entry += 4u;
|
|
break;
|
|
}
|
|
case kPlrEntryWriteDirect: {
|
|
const uint32_t pad = (head >> 20) & 3u;
|
|
const volatile uint32_t *payload = entry + 3u + pad;
|
|
|
|
if (!s->dry)
|
|
ft_tensix_noc_write((uint32_t)(uintptr_t)payload,
|
|
ft_tensix_noc_address(entry[1] & 0xFFFFu, entry[1] >> 16,
|
|
entry[2]),
|
|
count * 4u);
|
|
s->direct_pending = true;
|
|
entry += 3u + pad + count;
|
|
break;
|
|
}
|
|
case kPlrEntryRowCopy:
|
|
/* The row copy stages through scratch: every queued write lands first. */
|
|
if (!pull_flush_heads(s))
|
|
return kPlrErrInline;
|
|
if (!s->dry) {
|
|
ft_tensix_noc_write_barrier();
|
|
s->scratch_used = 0u;
|
|
if (!pull_row_copy(entry + 1))
|
|
return kPlrErrEntry;
|
|
}
|
|
entry += 10u;
|
|
break;
|
|
case kPlrEntrySegment:
|
|
case kPlrEntryEnd:
|
|
/* direct writes read the program: they land before its region can be reused */
|
|
if (s->direct_pending && !s->dry)
|
|
ft_tensix_noc_write_barrier();
|
|
s->direct_pending = false;
|
|
return pull_flush_heads(s) ? kPlrErrNone : kPlrErrInline;
|
|
default:
|
|
return kPlrErrEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PULL_MOVE_BYTES 0x8000u /* 32 KB of the 48 KB scratch a batch */
|
|
|
|
static bool pull_row_copy(const volatile uint32_t *a)
|
|
{
|
|
const uint32_t src_x = a[0], src_y = a[1], src_addr = a[2], src_row = a[3];
|
|
const uint32_t dst_x = a[4], dst_y = a[5], dst_addr = a[6], dst_row = a[7], tiles = a[8];
|
|
const uint32_t src_base = (src_row < 16u ? 0u : 1024u) + (src_row % 16u) * 32u;
|
|
const uint32_t dst_base = (dst_row < 16u ? 0u : 1024u) + (dst_row % 16u) * 32u;
|
|
const uint32_t per_batch = PULL_MOVE_BYTES / 128u; /* tiles a batch: 2 x 64 B each */
|
|
uint32_t t0;
|
|
|
|
if (src_row >= 32u || dst_row >= 32u)
|
|
return false;
|
|
for (t0 = 0; t0 < tiles; t0 += per_batch) {
|
|
const uint32_t n = (tiles - t0) < per_batch ? (tiles - t0) : per_batch;
|
|
uint32_t t, f;
|
|
|
|
for (t = 0; t < n; t++)
|
|
for (f = 0; f < 2u; f++) {
|
|
const uint32_t src = src_addr + (t0 + t) * 2048u + src_base + f * 512u;
|
|
|
|
ft_tensix_noc_read(ft_tensix_noc_address(src_x, src_y, src & ~63u),
|
|
PULL_SCRATCH_BASE + (2u * t + f) * 64u, 64u);
|
|
}
|
|
ft_tensix_noc_read_barrier();
|
|
for (t = 0; t < n; t++)
|
|
for (f = 0; f < 2u; f++) {
|
|
const uint32_t src = src_addr + (t0 + t) * 2048u + src_base + f * 512u;
|
|
|
|
ft_tensix_noc_write(PULL_SCRATCH_BASE + (2u * t + f) * 64u + (src & 32u),
|
|
ft_tensix_noc_address(dst_x, dst_y,
|
|
dst_addr + (t0 + t) * 2048u +
|
|
dst_base + f * 512u),
|
|
32u);
|
|
}
|
|
ft_tensix_noc_write_barrier();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** FILL: see kOpFill. A zero-length or misaligned request is refused, never issued. */
|
|
static bool pull_fill(const volatile uint32_t *a)
|
|
{
|
|
const uint32_t dst_x = a[0], dst_y = a[1], dst_addr = a[2], bytes = a[3], value = a[4];
|
|
volatile uint32_t *source = (volatile uint32_t *)(uintptr_t)PULL_SCRATCH_BASE;
|
|
uint32_t offset, word;
|
|
|
|
if (bytes == 0u || (bytes & 31u) != 0u || (dst_addr & 31u) != 0u)
|
|
return false;
|
|
for (word = 0; word < PULL_MOVE_BYTES / 4u; word++)
|
|
source[word] = value;
|
|
for (offset = 0; offset < bytes; offset += PULL_MOVE_BYTES) {
|
|
const uint32_t n = (bytes - offset) < PULL_MOVE_BYTES ? (bytes - offset) : PULL_MOVE_BYTES;
|
|
|
|
ft_tensix_noc_write(PULL_SCRATCH_BASE, ft_tensix_noc_address(dst_x, dst_y, dst_addr + offset),
|
|
n);
|
|
}
|
|
ft_tensix_noc_write_barrier();
|
|
return true;
|
|
}
|
|
|
|
/** COPY: see kOpCopy. A zero-length or misaligned request is refused, never issued. */
|
|
static bool pull_copy(const volatile uint32_t *a)
|
|
{
|
|
const uint32_t src_x = a[0], src_y = a[1], src_addr = a[2];
|
|
const uint32_t dst_x = a[3], dst_y = a[4], dst_addr = a[5], bytes = a[6];
|
|
const uint32_t half = PULL_SCRATCH_BYTES / 2u;
|
|
uint32_t offset, buffer = PULL_SCRATCH_BASE;
|
|
|
|
if (bytes == 0u || ((src_addr | dst_addr | bytes) & 63u) != 0u)
|
|
return false;
|
|
ft_tensix_noc_read(ft_tensix_noc_address(src_x, src_y, src_addr), buffer,
|
|
bytes < half ? bytes : half);
|
|
ft_tensix_noc_read_barrier();
|
|
for (offset = 0; offset < bytes; offset += half) {
|
|
const uint32_t n = (bytes - offset) < half ? (bytes - offset) : half;
|
|
const uint32_t next = offset + half;
|
|
const uint32_t other = buffer == PULL_SCRATCH_BASE ? PULL_SCRATCH_BASE + half
|
|
: PULL_SCRATCH_BASE;
|
|
|
|
if (next < bytes)
|
|
ft_tensix_noc_read(ft_tensix_noc_address(src_x, src_y, src_addr + next), other,
|
|
(bytes - next) < half ? (bytes - next) : half);
|
|
ft_tensix_noc_write(buffer, ft_tensix_noc_address(dst_x, dst_y, dst_addr + offset), n);
|
|
ft_tensix_noc_read_barrier();
|
|
ft_tensix_noc_write_barrier();
|
|
buffer = other;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void kernel_main(void)
|
|
{
|
|
ftq_context ctx;
|
|
|
|
ftq_context_init(&ctx);
|
|
|
|
volatile FTQ_L1_PTR uint32_t *control = ctx.control;
|
|
volatile FTQ_L1_PTR uint32_t *ring = ctx.ring;
|
|
const uint32_t capacity_mask = ctx.capacity_mask;
|
|
const uint32_t idle_spin_limit = ctx.idle_spin_limit;
|
|
|
|
control[kFieldState] = kStateBoot;
|
|
ftq_status_set(&ctx, kStateBoot);
|
|
if (control[kFieldMagic] != kMagic || capacity_mask == 0u ||
|
|
((capacity_mask + 1u) & capacity_mask) != 0u ||
|
|
control[kFieldCapacity] != capacity_mask + 1u || idle_spin_limit == 0u) {
|
|
control[kFieldError] = kErrBadCapacity;
|
|
control[kFieldState] = kStateExit;
|
|
ftq_status_set(&ctx, kStateExit);
|
|
return;
|
|
}
|
|
|
|
uint32_t tail = control[kFieldTail];
|
|
uint32_t completed = control[kFieldCompletedCount];
|
|
uint32_t idle_spins = 0u;
|
|
uint32_t heartbeat = control[kFieldHeartbeat];
|
|
uint32_t error = kErrNone;
|
|
bool running = true;
|
|
|
|
while (running) {
|
|
control[kFieldHeartbeat] = ++heartbeat;
|
|
if (control[kFieldStopRequest] != 0u)
|
|
break;
|
|
if (control[kFieldHead] == tail) {
|
|
control[kFieldState] = kStateIdle;
|
|
control[kFieldIdleSpins] = ++idle_spins;
|
|
if (idle_spins >= idle_spin_limit) {
|
|
error = kErrIdleTimeout;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
idle_spins = 0u;
|
|
control[kFieldState] = kStateExec;
|
|
|
|
volatile FTQ_L1_PTR uint32_t *descriptor = ring + (tail & capacity_mask) * kDescriptorWords;
|
|
const uint32_t opcode = descriptor[kDescOpcode];
|
|
const uint32_t token = descriptor[kDescToken];
|
|
bool item_completed = true;
|
|
|
|
control[kFieldLastOpcode] = opcode;
|
|
if (opcode == kOpPull) {
|
|
const volatile uint32_t *args =
|
|
(const volatile uint32_t *)(uintptr_t)descriptor[kDescA0];
|
|
pull_state s;
|
|
uint32_t segment, rc = kPlrErrNone;
|
|
|
|
s.header = (volatile uint32_t *)(uintptr_t)args[0];
|
|
s.k = args[1];
|
|
s.dry = (args[4] & 1u) != 0u;
|
|
s.patch_k = (args[4] & 2u) != 0u ? 0u : s.k;
|
|
s.red_bad_token = (args[4] & 4u) != 0u;
|
|
s.red_no_wait = (args[4] & 8u) != 0u;
|
|
s.red_group_token = (args[4] & 32u) != 0u;
|
|
s.scratch_used = 0u;
|
|
s.direct_pending = false;
|
|
s.heads = 0u;
|
|
s.log = (volatile uint32_t *)(uintptr_t)s.header[6];
|
|
s.log_words = 0u;
|
|
s.log_capacity_words = s.header[7] / 4u;
|
|
if (s.header[0] != kPlrMagic) {
|
|
rc = kPlrErrMagic;
|
|
} else {
|
|
const volatile uint32_t *segments =
|
|
(const volatile uint32_t *)((uintptr_t)s.header + s.header[5]);
|
|
|
|
s.queues = (const volatile uint32_t *)((uintptr_t)s.header + s.header[3]);
|
|
s.groups = (const volatile uint32_t *)((uintptr_t)s.header + s.header[15]);
|
|
const uint32_t started = ft_bh_wall_clock_low();
|
|
|
|
for (segment = args[2]; rc == kPlrErrNone && segment <= args[3]; segment++) {
|
|
if (segment >= s.header[4]) {
|
|
rc = kPlrErrSegment;
|
|
break;
|
|
}
|
|
rc = pull_segment(&s, (const volatile uint32_t *)((uintptr_t)s.header +
|
|
segments[segment]),
|
|
control);
|
|
}
|
|
s.header[12] += (ft_bh_wall_clock_low() - started) >> 4;
|
|
control[62] += (ft_bh_wall_clock_low() - started) >> 4;
|
|
}
|
|
s.header[8] = rc;
|
|
s.header[10] = s.log_words * 4u;
|
|
if (rc != kPlrErrNone) {
|
|
/* A FAILED REPLAY IS NOT COMPLETED: the host sees the count stop and reads
|
|
* header[8] for why. The kernel stays resident for the host's recovery. */
|
|
error = kErrBadParameters;
|
|
item_completed = false;
|
|
running = false;
|
|
}
|
|
} else if (opcode == kOpRowCopy) {
|
|
const volatile uint32_t *args =
|
|
(const volatile uint32_t *)(uintptr_t)descriptor[kDescA0];
|
|
|
|
if (!pull_row_copy(args)) {
|
|
error = kErrBadParameters;
|
|
item_completed = false;
|
|
running = false;
|
|
}
|
|
} else if (opcode == kOpFill) {
|
|
if (!pull_fill((const volatile uint32_t *)(uintptr_t)descriptor[kDescA0])) {
|
|
error = kErrBadParameters;
|
|
item_completed = false;
|
|
running = false;
|
|
}
|
|
} else if (opcode == kOpCopy) {
|
|
if (!pull_copy((const volatile uint32_t *)(uintptr_t)descriptor[kDescA0])) {
|
|
error = kErrBadParameters;
|
|
item_completed = false;
|
|
running = false;
|
|
}
|
|
} else if (opcode == kOpStop) {
|
|
running = false;
|
|
} else if (opcode != kOpNop) {
|
|
error = kErrBadOpcode;
|
|
item_completed = false;
|
|
running = false;
|
|
}
|
|
if (item_completed) {
|
|
tail++;
|
|
control[kFieldTail] = tail;
|
|
control[kFieldCompletedToken] = token;
|
|
control[kFieldCompletedCount] = ++completed;
|
|
}
|
|
}
|
|
control[kFieldError] = error;
|
|
control[kFieldState] = kStateExit;
|
|
ctx.error = error;
|
|
ftq_status_set(&ctx, kStateExit);
|
|
}
|