610 lines
23 KiB
C++
610 lines
23 KiB
C++
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file resident_queue_protocol.h
|
|
* @brief Device half of Foundry's resident command queue: the wire contract, the per-kernel
|
|
* queue context, and the stoppable, bounded waits every resident kernel uses.
|
|
*
|
|
* The host half is tt/ft_tt_queue.h; every number in the wire contract below must agree with
|
|
* it. The header is shared by the resident data-movement kernels (BRISC/NCRISC) and the
|
|
* resident compute kernels (TRISC, built once per thread), and it is a content input to all
|
|
* of their images.
|
|
*
|
|
* Safety model:
|
|
* - Every control word has exactly one writer. Completion is published by the last
|
|
* processor of the pipeline.
|
|
* - Every blocking CB wait can be asked to stop (the host writes the stop word) and is
|
|
* bounded by a spin limit.
|
|
* - An abort does not return early from the descriptor. It sets `aborting`; every later
|
|
* wait in the same descriptor then returns false at once, while the kernel's loops still
|
|
* run their full trip counts so the peer threads' hardware handshakes stay balanced. An
|
|
* abandoned descriptor is never counted complete.
|
|
* - NoC barriers and the MATH/PACK/dvalid hardware stalls are deliberately not stoppable.
|
|
*
|
|
* WIRE CONTRACT FORM: off-hardware tests parse this file line by line, so every wire constant
|
|
* is its own one-line anonymous enum `enum { name = value };`. Opcodes are the only
|
|
* identifiers here that start with `kOp`. kMatmulColumnBlock, kMatmulWords and kDescA2 are
|
|
* parsed as decimal.
|
|
*
|
|
* @status Complete for the data-movement and C99 compute arms.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
#ifndef RESIDENT_QUEUE_PROTOCOL_H
|
|
#define RESIDENT_QUEUE_PROTOCOL_H
|
|
|
|
#ifdef __cplusplus
|
|
#include <cstdint>
|
|
#else
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#endif
|
|
|
|
#if defined(COMPILE_FOR_TRISC) && defined(__cplusplus)
|
|
#error "resident_queue_protocol.h: there is no C++ compute arm; build compute kernels as C"
|
|
#endif
|
|
|
|
#if !defined(COMPILE_FOR_TRISC)
|
|
#include "ft_tensix_dataflow.h"
|
|
#else
|
|
#include "ft_tensix_compute_cb.h"
|
|
#include "ft_tensix_kernel_args.h"
|
|
#endif
|
|
|
|
/* Linkage of every helper here (family protocol headers spell it identically). */
|
|
#ifndef FTQ_INLINE
|
|
#ifdef __cplusplus
|
|
#define FTQ_INLINE inline
|
|
#else
|
|
#define FTQ_INLINE static inline
|
|
#endif
|
|
#endif
|
|
|
|
/* The compiler's L1 pointer attribute, spelled locally so this header does not depend on the
|
|
* register header for it. */
|
|
#ifndef FTQ_L1_PTR
|
|
#define FTQ_L1_PTR __attribute__((rvtt_l1_ptr))
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
#define FTQ_STATIC_ASSERT(condition, message) static_assert(condition, message)
|
|
#else
|
|
#define FTQ_STATIC_ASSERT(condition, message) _Static_assert(condition, message)
|
|
#endif
|
|
|
|
#define FTQ_RUNTIME_ARG(index) ft_tensix_runtime_arg(index)
|
|
|
|
/* ---- Control-block word indices (4-byte words from the control block base) ---- */
|
|
enum { kFieldMagic = 0 };
|
|
enum { kFieldCapacity = 1 };
|
|
enum { kFieldHead = 2 }; /* descriptors published by the host */
|
|
enum { kFieldTail = 3 }; /* descriptors taken by the reader */
|
|
enum { kFieldStopRequest = 4 }; /* host-written stop word */
|
|
enum { kFieldCompletedCount = 5 }; /* written by the last processor (writer) */
|
|
enum { kFieldCompletedToken = 6 };
|
|
enum { kFieldState = 7 };
|
|
enum { kFieldLastOpcode = 8 };
|
|
enum { kFieldError = 9 };
|
|
enum { kFieldIdleSpins = 10 };
|
|
enum { kFieldHeartbeat = 11 };
|
|
enum { kFieldGroupDone = 56 }; /* byte 0xE0: finished group sequence */
|
|
enum { kFieldIdleClock = 60 }; /* NCRISC's accumulated idle-for-work clock */
|
|
|
|
/* ---- Magic, states, errors ---- */
|
|
enum { kMagic = 0x46545131 }; /* "FTQ1" */
|
|
enum { kStatePoison = 0x7B1B1B1B }; /* host-written poison */
|
|
enum { kStateBoot = 0x51010001 };
|
|
enum { kStateIdle = 0x51010002 };
|
|
enum { kStateExec = 0x51010003 };
|
|
enum { kStateSemWait = 0x51010004 };
|
|
enum { kStateExit = 0x51010005 };
|
|
enum { kStateCbWaitFront = 0x51010006 };
|
|
enum { kStateCbReserveBack = 0x51010007 };
|
|
enum { kErrNone = 0 };
|
|
enum { kErrBadMagic = 1 };
|
|
enum { kErrBadOpcode = 2 };
|
|
enum { kErrIdleTimeout = 3 };
|
|
enum { kErrSemWaitTimeout = 4 };
|
|
enum { kErrBadCapacity = 5 };
|
|
enum { kErrCbWaitTimeout = 6 };
|
|
enum { kErrBadParameters = 7 };
|
|
enum { kErrCoopRoleMismatch = 8 };
|
|
enum { kErrCoopWritePointer = 9 };
|
|
enum { kErrCoopRendezvous = 10 };
|
|
enum { kErrCoopIssue = 11 };
|
|
enum { kErrCoopBadPartition = 12 };
|
|
|
|
/* ---- Opcodes ---- */
|
|
enum { kOpNop = 0x01 };
|
|
enum { kOpWriteWord = 0x02 };
|
|
enum { kOpL1Copy = 0x03 };
|
|
enum { kOpSemPost = 0x04 };
|
|
enum { kOpSemWait = 0x05 };
|
|
enum { kOpDelay = 0x06 };
|
|
enum { kOpStop = 0x07 };
|
|
enum { kOpMatmul = 0x08 };
|
|
enum { kOpEltwise = 0x09 };
|
|
enum { kOpSoftmax = 0x0A };
|
|
enum { kOpReduce = 0x0B };
|
|
enum { kOpEmbedding = 0x0C };
|
|
enum { kOpKvAppend = 0x0D };
|
|
enum { kOpRope = 0x0E };
|
|
enum { kOpTopk = 0x0F };
|
|
enum { kOpSample = 0x10 };
|
|
enum { kOpAttention = 0x11 };
|
|
enum { kOpGdn = 0x12 };
|
|
|
|
/* ---- Descriptor (one ring entry, 8 words) ---- */
|
|
enum { kDescriptorWords = 8 };
|
|
enum { kDescOpcode = 0 };
|
|
enum { kDescToken = 1 };
|
|
enum { kDescA0 = 2 };
|
|
enum { kDescA1 = 3 };
|
|
enum { kDescA2 = 4 };
|
|
enum { kDescA3 = 5 };
|
|
enum { kDescA4 = 6 };
|
|
enum { kDescA5 = 7 }; /* group sequence number; 0 = no group-completion post */
|
|
|
|
/* ---- Per-processor status slot (8 words = 32 bytes, so no two processors' heartbeats
|
|
* share a 16-byte L1 line) ---- */
|
|
enum { kStatusState = 0 };
|
|
enum { kStatusCursor = 1 };
|
|
enum { kStatusHeartbeat = 2 };
|
|
enum { kStatusError = 3 };
|
|
enum { kStatusLastOpcode = 4 };
|
|
enum { kStatusSpins = 5 };
|
|
enum { kStatusPayload = 6 }; /* 2 words: accumulated wait clock, wait count */
|
|
|
|
/* ---- Group completion block: [sequence, token] per member from byte 0x40 ---- */
|
|
enum { kGroupSlotOffsetWords = 16 };
|
|
enum { kGroupSlotWords = 2 };
|
|
|
|
/* ---- Pull completion (matmul writer), offsets from the control block ---- */
|
|
enum { kPullTableOffset = 0xF900 }; /* magic, count, then x | y << 16 per member */
|
|
enum { kPullScratchOffset = 0xFF00 }; /* 16-byte landing for the remote read */
|
|
enum { kPullMagic = 0x46545150 }; /* "FTQP" */
|
|
enum { kPullMaxMembers = 64 };
|
|
enum { kPullRedSkip = 0x80000000u }; /* bit 31 of a3: skip (unsigned enumerator) */
|
|
|
|
/* ---- Matmul parameter block (16 words at descriptor a0) ---- */
|
|
enum { kMatmulTileRows = 0 };
|
|
enum { kMatmulTileInner = 1 };
|
|
enum { kMatmulTileCols = 2 };
|
|
enum { kMatmulAX = 3 };
|
|
enum { kMatmulAY = 4 };
|
|
enum { kMatmulABase = 5 };
|
|
enum { kMatmulBX = 6 };
|
|
enum { kMatmulBY = 7 };
|
|
enum { kMatmulBBase = 8 };
|
|
enum { kMatmulCX = 9 };
|
|
enum { kMatmulCY = 10 };
|
|
enum { kMatmulCBase = 11 };
|
|
enum { kMatmulDepth = 12 };
|
|
enum { kMatmulBNMajor = 13 }; /* flag word: bit 0 N-major, 8 pipelined, 9 RED never-wait */
|
|
enum { kMatmulCooperative = 14 };
|
|
enum { kMatmulColumnBlock = 15 };
|
|
enum { kMatmulWords = 16 };
|
|
enum { kMatmulRow0 = 1u << 10 };
|
|
enum { kMatmulProbeNoRead = 1u << 12 };
|
|
enum { kMatmulProbeNoCompute = 1u << 13 };
|
|
enum { kMatmulProbeNoWrite = 1u << 14 };
|
|
enum { kMatmulWriterFlush = 1u << 15 };
|
|
enum { kMatmulAReuse = 1u << 18 };
|
|
enum { kMatmulAReuseRed = 1u << 19 };
|
|
|
|
/* ---- Runtime-argument layout (same on all five processors) ---- */
|
|
enum { kArgControl = 0 }; /* control-block L1 address */
|
|
enum { kArgCapacityMask = 1 }; /* capacity - 1 */
|
|
enum { kArgRing = 2 }; /* descriptor ring L1 address */
|
|
enum { kArgIdleSpinLimit = 3 };
|
|
enum { kArgStatus = 4 }; /* this processor's status slot, 0 = none */
|
|
enum { kArgCbSpinLimit = 5 };
|
|
enum { kArgGroupX = 6 };
|
|
enum { kArgGroupY = 7 };
|
|
enum { kArgGroupAddress = 8 }; /* 0 = no group completion */
|
|
enum { kArgGroupMember = 9 };
|
|
|
|
/* ---- Poll masks for blocked waits ---- */
|
|
enum { kStopPollMask = 0 }; /* poll the stop word every blocked pass */
|
|
enum { kHeartbeatPollMask = 0xFF }; /* bump the heartbeat every 256 blocked passes */
|
|
|
|
/* ---- Wait modes (compile-time argument passed as a parameter). Mode 2 (bound only, stop
|
|
* ignored) needs no code of its own: only mode 1 polls the stop word. ---- */
|
|
enum { kWaitUpstream = 0 }; /* plain blocking wait, no escape, no bound */
|
|
enum { kWaitStoppable = 1 }; /* stop-aware and bounded */
|
|
|
|
FTQ_STATIC_ASSERT(kStatePoison != kStateExit, "poison must not read as a clean exit");
|
|
FTQ_STATIC_ASSERT(kStatePoison != 0, "poison must not read as zero");
|
|
|
|
/** One processor's view of the queue. */
|
|
typedef struct ftq_context {
|
|
volatile FTQ_L1_PTR uint32_t *control;
|
|
volatile FTQ_L1_PTR uint32_t *ring;
|
|
volatile FTQ_L1_PTR uint32_t *status; /* may be null */
|
|
uint32_t capacity_mask;
|
|
uint32_t idle_spin_limit;
|
|
uint32_t cb_spin_limit;
|
|
uint32_t group_x;
|
|
uint32_t group_y;
|
|
uint32_t group_address; /* 0 = no group completion */
|
|
uint32_t group_member;
|
|
uint32_t cursor; /* descriptors this processor has taken */
|
|
uint32_t heartbeat;
|
|
uint32_t error;
|
|
bool aborting;
|
|
} ftq_context;
|
|
|
|
/** Fill `ctx` from this launch's runtime arguments. Statement order is kept as written. */
|
|
FTQ_INLINE void ftq_context_init(ftq_context *ctx)
|
|
{
|
|
ctx->control = (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)FTQ_RUNTIME_ARG(kArgControl);
|
|
ctx->capacity_mask = FTQ_RUNTIME_ARG(kArgCapacityMask);
|
|
ctx->ring = (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)FTQ_RUNTIME_ARG(kArgRing);
|
|
ctx->idle_spin_limit = FTQ_RUNTIME_ARG(kArgIdleSpinLimit);
|
|
const uint32_t status_address = FTQ_RUNTIME_ARG(kArgStatus);
|
|
ctx->status = status_address ? (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)status_address
|
|
: (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)0;
|
|
ctx->cb_spin_limit = FTQ_RUNTIME_ARG(kArgCbSpinLimit);
|
|
if (ctx->status) {
|
|
ctx->status[kStatusPayload] = 0u;
|
|
ctx->status[kStatusPayload + 1] = 0u;
|
|
}
|
|
#if defined(COMPILE_FOR_NCRISC)
|
|
ctx->control[kFieldIdleClock] = 0u;
|
|
#endif
|
|
ctx->group_x = FTQ_RUNTIME_ARG(kArgGroupX);
|
|
ctx->group_y = FTQ_RUNTIME_ARG(kArgGroupY);
|
|
ctx->group_address = FTQ_RUNTIME_ARG(kArgGroupAddress);
|
|
ctx->group_member = FTQ_RUNTIME_ARG(kArgGroupMember);
|
|
ctx->cursor = 0u;
|
|
ctx->heartbeat = 0u;
|
|
ctx->error = kErrNone;
|
|
ctx->aborting = false;
|
|
}
|
|
|
|
/** Publish state, cursor, a fresh heartbeat and the error into the status slot. */
|
|
FTQ_INLINE void ftq_status_set(ftq_context *ctx, uint32_t state)
|
|
{
|
|
if (ctx->status) {
|
|
ctx->status[kStatusState] = state;
|
|
ctx->status[kStatusCursor] = ctx->cursor;
|
|
ctx->status[kStatusHeartbeat] = ++ctx->heartbeat;
|
|
ctx->status[kStatusError] = ctx->error;
|
|
}
|
|
}
|
|
|
|
FTQ_INLINE void ftq_status_opcode(const ftq_context *ctx, uint32_t opcode)
|
|
{
|
|
if (ctx->status)
|
|
ctx->status[kStatusLastOpcode] = opcode;
|
|
}
|
|
|
|
FTQ_INLINE bool ftq_stop_requested(const ftq_context *ctx)
|
|
{
|
|
return ctx->control[kFieldStopRequest] != 0u;
|
|
}
|
|
|
|
/** Abandon the current descriptor: later waits return false at once. The first non-zero
|
|
* reason is kept. */
|
|
FTQ_INLINE void ftq_abort(ftq_context *ctx, uint32_t reason)
|
|
{
|
|
ctx->aborting = true;
|
|
if (reason != kErrNone && ctx->error == kErrNone)
|
|
ctx->error = reason;
|
|
if (ctx->status)
|
|
ctx->status[kStatusError] = ctx->error;
|
|
}
|
|
|
|
/** Add one blocked wait (clock since `started`, in 16-cycle units) to the status payload. */
|
|
FTQ_INLINE void ftq_wait_account(ftq_context *ctx, uint32_t started)
|
|
{
|
|
if (ctx->status) {
|
|
ctx->status[kStatusPayload] += (ft_bh_wall_clock_low() - started) >> 4;
|
|
ctx->status[kStatusPayload + 1] += 1u;
|
|
}
|
|
}
|
|
|
|
/** Validate the control block and the launch parameters; sets ctx->error on failure. */
|
|
FTQ_INLINE bool ftq_check_control(ftq_context *ctx)
|
|
{
|
|
if (ctx->control[kFieldMagic] != (uint32_t)kMagic) {
|
|
ctx->error = kErrBadMagic;
|
|
return false;
|
|
}
|
|
if (ctx->capacity_mask == 0u || ((ctx->capacity_mask + 1u) & ctx->capacity_mask) != 0u) {
|
|
ctx->error = kErrBadCapacity;
|
|
return false;
|
|
}
|
|
if (ctx->control[kFieldCapacity] != ctx->capacity_mask + 1u) {
|
|
ctx->error = kErrBadCapacity;
|
|
return false;
|
|
}
|
|
if (ctx->cb_spin_limit == 0u || ctx->idle_spin_limit == 0u) {
|
|
ctx->error = kErrBadParameters;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** The ring entry at this processor's cursor. */
|
|
FTQ_INLINE volatile FTQ_L1_PTR uint32_t *ftq_descriptor(const ftq_context *ctx)
|
|
{
|
|
return ctx->ring + (ctx->cursor & ctx->capacity_mask) * kDescriptorWords;
|
|
}
|
|
|
|
/** The parameter block a descriptor points at (word a0). */
|
|
FTQ_INLINE volatile FTQ_L1_PTR uint32_t *ftq_parameters(volatile FTQ_L1_PTR uint32_t *descriptor)
|
|
{
|
|
return (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)descriptor[kDescA0];
|
|
}
|
|
|
|
/** Idle until control[barrier_field] differs from the cursor. False = leave the resident
|
|
* loop (stop requested, or the idle bound ran out with kErrIdleTimeout). */
|
|
FTQ_INLINE bool ftq_wait_for_work(ftq_context *ctx, uint32_t barrier_field)
|
|
{
|
|
uint32_t idle_spins = 0u;
|
|
#if defined(COMPILE_FOR_NCRISC)
|
|
const uint32_t idle_from = ft_bh_wall_clock_low();
|
|
#endif
|
|
|
|
for (;;) {
|
|
ctx->heartbeat++;
|
|
if (ctx->status)
|
|
ctx->status[kStatusHeartbeat] = ctx->heartbeat;
|
|
if (ftq_stop_requested(ctx))
|
|
return false;
|
|
if (ctx->control[barrier_field] != ctx->cursor) {
|
|
#if defined(COMPILE_FOR_NCRISC)
|
|
if (idle_spins != 0u)
|
|
ctx->control[kFieldIdleClock] += (ft_bh_wall_clock_low() - idle_from) >> 4;
|
|
#endif
|
|
return true;
|
|
}
|
|
if (ctx->status)
|
|
ctx->status[kStatusState] = kStateIdle;
|
|
if (++idle_spins >= ctx->idle_spin_limit) {
|
|
ctx->error = kErrIdleTimeout;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Wait until control[field] >= value; stoppable and bounded by the CB spin limit. */
|
|
FTQ_INLINE bool ftq_wait_until_reaches(ftq_context *ctx, uint32_t field, uint32_t value)
|
|
{
|
|
uint32_t spins = 0u;
|
|
|
|
while (ctx->control[field] < value) {
|
|
ctx->heartbeat++;
|
|
if (ctx->status)
|
|
ctx->status[kStatusHeartbeat] = ctx->heartbeat;
|
|
if (ftq_stop_requested(ctx)) {
|
|
ftq_abort(ctx, kErrNone);
|
|
return false;
|
|
}
|
|
if (++spins >= ctx->cb_spin_limit) {
|
|
ftq_abort(ctx, kErrCbWaitTimeout);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* What a blocked CB wait does after an unsatisfied test: start the wait clock on the first
|
|
* pass, publish a heartbeat every 256 passes, and give up (aborting the descriptor) when the
|
|
* stop word is set in stoppable mode or the spin bound runs out. Expects `ctx` and
|
|
* `wait_mode` in scope; returns false from the enclosing function. */
|
|
#define FTQ_CB_WAIT_ESCAPE(spins, wait_started) \
|
|
do { \
|
|
if ((spins) == 0u) \
|
|
(wait_started) = ft_bh_wall_clock_low(); \
|
|
(spins)++; \
|
|
if (((spins) & (uint32_t)kHeartbeatPollMask) == 0u && ctx->status) { \
|
|
ctx->heartbeat++; \
|
|
ctx->status[kStatusHeartbeat] = ctx->heartbeat; \
|
|
ctx->status[kStatusSpins] = (spins); \
|
|
} \
|
|
if (((spins) & (uint32_t)kStopPollMask) == 0u) { \
|
|
if (wait_mode == (uint32_t)kWaitStoppable && ftq_stop_requested(ctx)) { \
|
|
ftq_abort(ctx, kErrNone); \
|
|
return false; \
|
|
} \
|
|
if ((spins) >= ctx->cb_spin_limit) { \
|
|
ftq_abort(ctx, kErrCbWaitTimeout); \
|
|
return false; \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#if !defined(COMPILE_FOR_TRISC)
|
|
|
|
/** Data-movement consumer: wait until `num_pages` pages are readable in CB `operand`. */
|
|
FTQ_INLINE bool ftq_cb_wait_front_stoppable(ftq_context *ctx, int32_t operand, int32_t num_pages,
|
|
uint32_t wait_mode)
|
|
{
|
|
uint32_t pages_acked;
|
|
uintptr_t pages_received_pointer;
|
|
uint16_t pages_received;
|
|
uint32_t spins = 0u;
|
|
uint32_t wait_started = 0u;
|
|
|
|
if (wait_mode == (uint32_t)kWaitUpstream) {
|
|
ft_tensix_cb_wait_front(operand, num_pages);
|
|
return true;
|
|
}
|
|
if (ctx->aborting)
|
|
return false;
|
|
pages_acked = ft_tensix_cb_pages_acked_pointer(operand)[0];
|
|
pages_received_pointer = (uintptr_t)ft_tensix_cb_pages_received_pointer(operand);
|
|
do {
|
|
pages_received =
|
|
((uint16_t)ft_bh_reg_read(pages_received_pointer)) - (uint16_t)pages_acked;
|
|
if (pages_received >= (uint16_t)num_pages)
|
|
break;
|
|
FTQ_CB_WAIT_ESCAPE(spins, wait_started);
|
|
} while (true);
|
|
if (spins != 0u)
|
|
ftq_wait_account(ctx, wait_started);
|
|
return true;
|
|
}
|
|
|
|
/** Data-movement producer: wait until `num_pages` pages are free in CB `operand`. */
|
|
FTQ_INLINE bool ftq_cb_reserve_back_stoppable(ftq_context *ctx, int32_t operand,
|
|
int32_t num_pages, uint32_t wait_mode)
|
|
{
|
|
uintptr_t pages_acked_pointer;
|
|
uint32_t pages_received;
|
|
int32_t free_space_pages;
|
|
uint32_t spins = 0u;
|
|
uint32_t wait_started = 0u;
|
|
|
|
if (wait_mode == (uint32_t)kWaitUpstream) {
|
|
ft_tensix_cb_reserve_back(operand, num_pages);
|
|
return true;
|
|
}
|
|
if (ctx->aborting)
|
|
return false;
|
|
pages_acked_pointer = (uintptr_t)ft_tensix_cb_pages_acked_pointer(operand);
|
|
pages_received = ft_tensix_cb_pages_received_pointer(operand)[0];
|
|
do {
|
|
uint16_t pages_acked;
|
|
uint16_t free_space_pages_wrap;
|
|
|
|
ft_bh_l1_cache_invalidate();
|
|
pages_acked = (uint16_t)ft_bh_reg_read(pages_acked_pointer);
|
|
free_space_pages_wrap = ft_tensix_cb((uint32_t)operand)->fifo_num_pages -
|
|
((uint16_t)pages_received - pages_acked);
|
|
free_space_pages = (int32_t)free_space_pages_wrap;
|
|
if (free_space_pages >= num_pages)
|
|
break;
|
|
FTQ_CB_WAIT_ESCAPE(spins, wait_started);
|
|
} while (true);
|
|
if (spins != 0u)
|
|
ftq_wait_account(ctx, wait_started);
|
|
return true;
|
|
}
|
|
|
|
/** Write [sequence, token] into this member's slot of the group block on another core, as
|
|
* two inline word writes, token first. Called after completed_count is published; a refusal
|
|
* aborts with kErrBadParameters and is not rolled back. */
|
|
FTQ_INLINE void ftq_group_post(ftq_context *ctx, uint32_t sequence, uint32_t token)
|
|
{
|
|
uint32_t slot_address;
|
|
|
|
if (ctx->group_address == 0u || sequence == 0u)
|
|
return;
|
|
slot_address =
|
|
ctx->group_address + (kGroupSlotOffsetWords + ctx->group_member * kGroupSlotWords) * 4u;
|
|
if (ft_tensix_noc_inline_dword_write_l1(
|
|
ft_tensix_noc_address(ctx->group_x, ctx->group_y, slot_address + 4u), token) !=
|
|
FT_TENSIX_NOC_ISSUED) {
|
|
ftq_abort(ctx, kErrBadParameters);
|
|
return;
|
|
}
|
|
ft_tensix_noc_write_barrier();
|
|
if (ft_tensix_noc_inline_dword_write_l1(
|
|
ft_tensix_noc_address(ctx->group_x, ctx->group_y, slot_address), sequence) !=
|
|
FT_TENSIX_NOC_ISSUED) {
|
|
ftq_abort(ctx, kErrBadParameters);
|
|
return;
|
|
}
|
|
ft_tensix_noc_write_barrier();
|
|
}
|
|
|
|
#else /* COMPILE_FOR_TRISC */
|
|
|
|
/** Unpack-thread consumer wait. The acked count is the thread's own 16-bit record field,
|
|
* not the stream register. */
|
|
FTQ_INLINE bool ftq_llk_wait_tiles_body(ftq_context *ctx, int32_t operand, int32_t num_pages,
|
|
uint32_t wait_mode)
|
|
{
|
|
uint32_t input = (uint32_t)operand;
|
|
volatile FT_BH_REG_PTR uint32_t *tiles_received_pointer =
|
|
ft_tensix_cb_pages_received_pointer(operand);
|
|
uint16_t num_tiles_u = (uint16_t)num_pages;
|
|
uint16_t tiles_received;
|
|
uint16_t num_tiles_recv;
|
|
uint32_t spins = 0u;
|
|
uint32_t wait_started = 0u;
|
|
|
|
do {
|
|
tiles_received = (uint16_t)ft_bh_reg_read((uint32_t)(uintptr_t)tiles_received_pointer);
|
|
num_tiles_recv =
|
|
(uint16_t)(tiles_received - ft_tensix_cb(input)->tiles.counters.tiles_acked);
|
|
if (num_tiles_recv >= num_tiles_u) {
|
|
if (spins != 0u)
|
|
ftq_wait_account(ctx, wait_started);
|
|
return true;
|
|
}
|
|
FTQ_CB_WAIT_ESCAPE(spins, wait_started);
|
|
} while (true);
|
|
}
|
|
|
|
/** Compute consumer wait; only the unpack thread waits, the others just honour aborting. */
|
|
FTQ_INLINE bool ftq_cb_wait_front_stoppable(ftq_context *ctx, int32_t operand, int32_t num_pages,
|
|
uint32_t wait_mode)
|
|
{
|
|
bool arrived = true;
|
|
(void)operand;
|
|
(void)num_pages;
|
|
|
|
if (wait_mode == (uint32_t)kWaitUpstream) {
|
|
FT_TENSIX_UNPACK(ft_tensix_compute_cb_wait_front((uint32_t)operand, (uint32_t)num_pages));
|
|
return true;
|
|
}
|
|
if (ctx->aborting)
|
|
return false;
|
|
FT_TENSIX_UNPACK((arrived = ftq_llk_wait_tiles_body(ctx, operand, num_pages, wait_mode)));
|
|
return arrived;
|
|
}
|
|
|
|
/** Pack-thread producer wait: free tiles = pages minus (received - acked), received being the
|
|
* thread's own record field, read once. */
|
|
FTQ_INLINE bool ftq_llk_wait_for_free_tiles_body(ftq_context *ctx, int32_t operand,
|
|
int32_t num_pages, uint32_t wait_mode)
|
|
{
|
|
uint32_t output = (uint32_t)operand;
|
|
volatile FT_BH_REG_PTR uint32_t *tiles_acked_pointer =
|
|
ft_tensix_cb_pages_acked_pointer(operand);
|
|
uint16_t tiles_received = ft_tensix_cb(output)->tiles.counters.tiles_received;
|
|
int32_t free_tiles;
|
|
uint32_t spins = 0u;
|
|
uint32_t wait_started = 0u;
|
|
|
|
do {
|
|
uint16_t tiles_acked = (uint16_t)ft_bh_reg_read((uint32_t)(uintptr_t)tiles_acked_pointer);
|
|
uint16_t free_tiles_wrap = (uint16_t)(ft_tensix_cb(output)->fifo_num_pages -
|
|
(uint16_t)(tiles_received - tiles_acked));
|
|
free_tiles = (int32_t)free_tiles_wrap;
|
|
if (free_tiles >= (int32_t)num_pages) {
|
|
if (spins != 0u)
|
|
ftq_wait_account(ctx, wait_started);
|
|
return true;
|
|
}
|
|
FTQ_CB_WAIT_ESCAPE(spins, wait_started);
|
|
} while (true);
|
|
}
|
|
|
|
/** Compute producer wait; only the pack thread waits, the others just honour aborting. */
|
|
FTQ_INLINE bool ftq_cb_reserve_back_stoppable(ftq_context *ctx, int32_t operand,
|
|
int32_t num_pages, uint32_t wait_mode)
|
|
{
|
|
bool reserved = true;
|
|
(void)operand;
|
|
(void)num_pages;
|
|
|
|
if (wait_mode == (uint32_t)kWaitUpstream) {
|
|
FT_TENSIX_PACK(
|
|
ft_tensix_compute_cb_reserve_back((uint32_t)operand, (uint32_t)num_pages));
|
|
return true;
|
|
}
|
|
if (ctx->aborting)
|
|
return false;
|
|
FT_TENSIX_PACK(
|
|
(reserved = ftq_llk_wait_for_free_tiles_body(ctx, operand, num_pages, wait_mode)));
|
|
return reserved;
|
|
}
|
|
|
|
#endif /* COMPILE_FOR_TRISC */
|
|
|
|
#endif /* RESIDENT_QUEUE_PROTOCOL_H */
|