Skip to main content

<alp/tmu.h> — Transcendental Math

Twelve portable single-precision math primitives that the active SoM's hardware math unit can accelerate when one is present. Every call is stateless — there is no alp_tmu_open, no handle, and no pool.

The value over calling libm directly is the seam: a single place where future SoCs can register their own math accelerators without the application changing.

#include <alp/tmu.h>

Quick example

float s;
if (alp_tmu_sin(0.5f, &s) == ALP_OK) {
// s ~= 0.479425539
}

float h;
alp_tmu_hypot(3.0f, 4.0f, &h); // h == 5.0

Every call takes its output through a pointer and returns an alp_status_t, because the accelerated path can fail in ways libm cannot (a bridge timeout, an unsupported function). Check the status — do not assume out was written.

Which silicon actually has a math accelerator?

SoM familyPathReality
V2N family (E1M-V2N101/102, E1M-V2M101/102)GD32G553 supervisor MCU's TMU (CORDIC) engine, reached over the bridge protocol (CMD_TMU_COMPUTE)Compiled only when CONFIG_ALP_SDK_V2N_SUPERVISOR=y. Three of the twelve functions are not natively supported — see below.
AEN family (Alif Ensemble)libmThere is no Alif TMU backend in the SDK. Despite the Ensemble's FPU, alp_tmu_* on AEN is sinf/cosf/…
Every other Zephyr SoMlibmSame wildcard backend, libm branch.
Yocto / baremetallibm

:::caution The hardware path is on the supervisor MCU, not the application SoC On the V2N family the CORDIC engine belongs to the GD32G553 supervisor, so every accelerated call is a round-trip over SPI or I²C — the GD32 computes and returns the result. That is a fundamentally different cost profile from an on-die FPU instruction. Do not assume alp_tmu_* is faster than libm on the application core; measure it. The surface exists for portability and offload, not as a guaranteed speedup. :::

V2N: tan, exp, and tanh are ALP_ERR_NOSUPPORT

The GD32G5's TMU has no native mode for tan, exp, or tanh. The bridge firmware's dispatch table marks all three as unsupported and returns a not-implemented status, and the host-side backend passes that straight back:

  • The TMU dispatcher resolves its backend once with alp_backend_select() and does not call alp_backend_select_next().
  • So on a CONFIG_ALP_SDK_V2N_SUPERVISOR=y build there is no fall-through to libm for these three. alp_tmu_tan, alp_tmu_exp, and alp_tmu_tanh return ALP_ERR_NOSUPPORT on that configuration.

Composing them host-side (tan = sin/cos, tanh = sinh/cosh, exp = cosh + sinh) is noted as possible in the sources but is not implemented today. Portable code that needs these three on V2N must call libm directly or handle ALP_ERR_NOSUPPORT.

The other nine (sin, cos, atan, atan2, sqrt, log, sinh, cosh, hypot) map to native TMU modes.

:::caution V2N trig domain: |x| <= pi, no range reduction On the bridge path, F32 trig angles are radians and the firmware does not range-reduce — the usable domain is |x| <= pi. The libm path has no such restriction. Code that feeds unreduced angles (an accumulating phase, say) will diverge between SoMs unless you reduce the argument yourself. :::

Functions

All twelve share the same shape. out must be non-NULL — the dispatcher rejects NULL with ALP_ERR_INVAL before selecting a backend.

CallComputes
alp_tmu_sin(float in_a, float *out)sin(in_a), in_a in radians
alp_tmu_cos(float in_a, float *out)cos(in_a), in_a in radians
alp_tmu_tan(float in_a, float *out)tan(in_a), in_a in radians — NOSUPPORT on V2N bridge builds
alp_tmu_atan(float in_a, float *out)atan(in_a)[-pi/2, pi/2]
alp_tmu_atan2(float in_a, float in_b, float *out)atan2(y=in_a, x=in_b)[-pi, pi], quadrant-correct
alp_tmu_sqrt(float in_a, float *out)sqrt(in_a), in_a non-negative
alp_tmu_log(float in_a, float *out)ln(in_a), in_a strictly positive
alp_tmu_exp(float in_a, float *out)e^in_aNOSUPPORT on V2N bridge builds
alp_tmu_sinh(float in_a, float *out)sinh(in_a)
alp_tmu_cosh(float in_a, float *out)cosh(in_a)
alp_tmu_tanh(float in_a, float *out)tanh(in_a)NOSUPPORT on V2N bridge builds
alp_tmu_hypot(float in_a, float in_b, float *out)sqrt(in_a² + in_b²)

alp_tmu_hypot is worth preferring over a hand-rolled sqrtf(a*a + b*b): it matches C99 hypotf semantics, and on the V2N backend it dispatches through the CORDIC "modulus" mode, giving a single-pass result without the intermediate overflow risk.

Status codes

CodeWhen
ALP_OKSuccess; out written.
ALP_ERR_INVALout is NULL.
ALP_ERR_NOSUPPORTThe selected backend's hardware path does not implement this function (V2N: tan / exp / tanh). The libm fallback never returns this.
ALP_ERR_NOT_IMPLEMENTEDNo backend resolved for the class, or its ops slot is NULL.
ALP_ERR_NOT_READYThe V2N supervisor is enabled but no bridge bus is configured.
ALP_ERR_OUT_OF_RANGEV2N backend only: input outside the function's mathematical domain (sqrt of a negative, log of zero).
ALP_ERR_IOBackend / bridge failure.

Domain and accuracy

The surface mirrors C99 semantics, but the two paths disagree on how they report a domain error:

Inputlibm pathV2N bridge path
sqrt(-1.0f)out = NaN, returns ALP_OKreturns ALP_ERR_OUT_OF_RANGE
log(0.0f)out = -inf, returns ALP_OKreturns ALP_ERR_OUT_OF_RANGE

Portable code that must discriminate should check the status and test the result with isnanf — neither check alone covers both backends.

Accuracy: the libm fallback delivers the platform's single-precision math accuracy directly. The CORDIC-backed V2N path is accurate to a few LSBs of binary32. Callers needing bit-exact reproducibility across SoMs (regression-test fingerprints, for example) must tolerate unit-in-last-place differences — this surface does not promise cross-SoM bit-identity.

Backends

Backendsilicon_refPriorityNotes
zephyr_drv"*"100Single wildcard row covering every Zephyr-shaped SoM. The libm-vs-bridge split is internal: CONFIG_ALP_SDK_V2N_SUPERVISOR=y compiles the bridge path, every other build compiles the libm path.
sw_fallback"*"0libm.

Because the wildcard zephyr_drv sits at priority 100 and sw_fallback at 0, zephyr_drv always wins on Zephyr builds (selection rules) — and since the dispatcher never falls through, sw_fallback is effectively the non-Zephyr floor rather than a runtime safety net.

The dispatcher caches the selected ops vtable on first call, so the steady-state hot path is two loads, a branch, and an indirect call. The cache is lock-free but published through acquire/release atomics; the registry is immutable after link and alp_backend_select() is deterministic, so racing first-callers resolve the identical backend.

The public header carries no gd32 references — the GD32 dispatch lives entirely in the backend, per the SDK's portability rule.

ABI status

[ABI-EXPERIMENTAL] — wave-1 GD32 CORDIC TMU helpers. The surface is limited and consolidation with <alp/dsp.h> is under consideration, so this header may be folded or renamed before v1.0. Pin your SDK to a specific commit if you depend on it.

See also

Questions about this page? Discuss in Community Forum