diff --git a/examples/natmod/deepcraft/Makefile b/examples/natmod/deepcraft/Makefile index c50e60668f6d8..328374efe83e2 100644 --- a/examples/natmod/deepcraft/Makefile +++ b/examples/natmod/deepcraft/Makefile @@ -27,7 +27,9 @@ endif CFLAGS += -Wno-error=implicit-function-declaration override LIBGCC_PATH := gcc/lib/gcc/arm-none-eabi/11.3.1/thumb/v7e-m+fp/hard/libgcc.a -override LIBM_PATH := gcc/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libm.a +# libm.a is excluded: expf() is provided directly in dc_mp_iface.c. +# mpy_ld raises "duplicate symbol" if libm.a is also linked. +override LIBM_PATH := include $(MPY_DIR)/py/dynruntime.mk diff --git a/examples/natmod/deepcraft/dc_mp_iface.c b/examples/natmod/deepcraft/dc_mp_iface.c index 59766d7ee2212..d6efb7bc66d5f 100644 --- a/examples/natmod/deepcraft/dc_mp_iface.c +++ b/examples/natmod/deepcraft/dc_mp_iface.c @@ -7,6 +7,112 @@ void *memcpy(void *dst, const void *src, size_t n) { void *memset(void *s, int c, size_t n) { return mp_fun_table.memset_(s, c, n); } + +// ============================================================================= +// libm shims — safe replacements for arm-none-eabi Newlib math functions. +// +// libm.a is NOT linked (LIBM_PATH := in Makefile) because mpy_ld cannot +// resolve libm's internal Newlib dependencies (__errno, _REENT, kernel +// helpers). Calling an unresolved libm symbol causes a silent HardFault. +// +// These shims use only integer ops and IEEE 754 bit manipulation — no +// external dependencies. They cover all single-precision functions +// commonly used by DEEPCRAFT / TensorFlow Lite inference models. +// +// If a future model needs a function not listed here, the build will fail +// with a clear "undefined symbol" link error. +// ============================================================================= + +// Helper: bit-cast float - unsigned int without UB +static inline unsigned int _f2u(float f) { union { float f; unsigned int u; } v; v.f = f; return v.u; } +static inline float _u2f(unsigned int u) { union { float f; unsigned int u; } v; v.u = u; return v.f; } + +// --- expf --- +// Degree-5 minimax polynomial + IEEE 754 exponent scaling. +// Max error ~1.5 ULP for |x| <= 88. +float expf(float x) { + if (x > 88.0f) return 3.40282347e+38f; + if (x < -88.0f) return 0.0f; + int n = (int)(x * 1.4426950409f + (x >= 0.0f ? 0.5f : -0.5f)); + float r = x - (float)n * 0.6931471806f; + float p = 1.0f + r * (1.0f + r * (0.5f + r * (0.16666667f + + r * (0.041666667f + r * 0.0083333333f)))); + return p * _u2f((unsigned int)((n + 127) << 23)); +} + +// --- logf --- +// Natural log via exponent extraction + polynomial on [sqrt(0.5), sqrt(2)]. +float logf(float x) { + if (x <= 0.0f) return -3.40282347e+38f; + int e = (int)((_f2u(x) >> 23) & 0xFF) - 127; + float m = _u2f((_f2u(x) & 0x007FFFFFu) | 0x3F000000u); // m in [0.5, 1) + if (m < 0.70710678f) { m *= 2.0f; e -= 1; } + float f = (m - 1.0f) / (m + 1.0f), f2 = f * f; + float p = f * (2.0f + f2 * (0.66666667f + f2 * (0.4f + f2 * + (0.28571429f + f2 * 0.22222222f)))); + return p + (float)e * 0.6931471806f; +} + +// --- log2f --- +float log2f(float x) { return logf(x) * 1.4426950409f; } + +// --- log10f --- +float log10f(float x) { return logf(x) * 0.4342944819f; } + +// --- tanhf --- +// tanh(x) = (e^2x - 1)/(e^2x + 1); clamped for |x| > 9 (result = ±1). +float tanhf(float x) { + if (x > 9.0f) return 1.0f; + if (x < -9.0f) return -1.0f; + float e2x = expf(2.0f * x); + return (e2x - 1.0f) / (e2x + 1.0f); +} + +// --- sqrtf --- +// Three Newton-Raphson iterations on an IEEE 754 seed estimate (~6 correct bits). +// On Cortex-M4F the compiler may emit vsqrt.f32 directly and never call this. +float sqrtf(float x) { + if (x <= 0.0f) return 0.0f; + float r = _u2f(0x1fbb4000u + (_f2u(x) >> 1)); // seed: ~half the exponent + r = 0.5f * (r + x / r); + r = 0.5f * (r + x / r); + r = 0.5f * (r + x / r); + return r; +} + +// --- fabsf --- (often compiled to vabs.f32 inline, but shim for safety) +float fabsf(float x) { return _u2f(_f2u(x) & 0x7FFFFFFFu); } + +// --- floorf --- +float floorf(float x) { int i = (int)x; return (float)(i - (x < (float)i)); } + +// --- ceilf --- +float ceilf(float x) { int i = (int)x; return (float)(i + (x > (float)i)); } + +// --- roundf --- +float roundf(float x) { return (x >= 0.0f) ? floorf(x + 0.5f) : ceilf(x - 0.5f); } + +// --- fmodf --- +float fmodf(float x, float y) { + if (y == 0.0f) return 0.0f; + float q = x / y; + int n = (int)q; + return x - (float)n * y; +} + +// --- powf --- +// a^b = exp(b * ln(a)); handles integer-exponent cases first for accuracy. +float powf(float a, float b) { + if (b == 0.0f) return 1.0f; + if (a == 0.0f) return 0.0f; + if (a < 0.0f) { + int ib = (int)b; + if ((float)ib != b) return 0.0f; // non-integer exponent of negative base + return (ib & 1) ? -expf(b * logf(-a)) : expf(b * logf(-a)); + } + return expf(b * logf(a)); +} + #endif int native_errno=0; @@ -37,7 +143,7 @@ static mp_obj_t dc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_k mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) { // This must be first, it sets up the globals dict and other things MP_DYNRUNTIME_INIT_ENTRY - + // Populate type dc_type.base.type = (void*)&mp_type_type; dc_type.flags = MP_TYPE_FLAG_NONE; @@ -57,4 +163,4 @@ mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *a // This must be last, it restores the globals dict MP_DYNRUNTIME_INIT_EXIT -} +} \ No newline at end of file diff --git a/examples/natmod/deepcraft/mp_src.c b/examples/natmod/deepcraft/mp_src.c index 0554f44114b40..da65aad95e23a 100644 --- a/examples/natmod/deepcraft/mp_src.c +++ b/examples/natmod/deepcraft/mp_src.c @@ -30,24 +30,24 @@ mp_obj_t init(mp_obj_t self_in){ mp_obj_t enqueue(mp_obj_t self_in, const mp_obj_t data_in_obj){ dc_obj_t *self = MP_OBJ_TO_PTR(self_in); - + // Check if model is initialized if(!self->model_state){ - mp_raise_ValueError("Model should be initialized first."); + mp_raise_ValueError(MP_ERROR_TEXT("Model should be initialized first.")); } - float data_in[self->model_in_dim]; - mp_obj_t *data_in_items; - size_t len; - mp_obj_get_array(data_in_obj, &len, &data_in_items); + // Use buffer protocol (array.array('f', ...)) instead of mp_obj_get_float to avoid + // mp_fun_table ABI mismatch issues. + // This is the same approach used by dequeue() and avoids float extraction entirely. + mp_buffer_info_t buf_info; + mp_get_buffer(data_in_obj, &buf_info, MP_BUFFER_READ); + float *data_in = (float *)buf_info.buf; + size_t len = buf_info.len / sizeof(float); if (len != self->model_in_dim) { - mp_raise_ValueError("data_in must be a list of floats with size matching to input dimensions to model. Check using get_model_input_dim()."); + mp_raise_ValueError(MP_ERROR_TEXT("data_in must be array.array('f', ...) with length matching model input dimensions.")); } - for (int i = 0; i < self->model_in_dim; i++) { - data_in[i] = mp_obj_get_float(data_in_items[i]); - } int result = IMAI_enqueue(data_in); return MP_OBJ_NEW_SMALL_INT(result); } @@ -100,4 +100,4 @@ static const mp_obj_fun_builtin_fixed_t get_model_input_dim_obj = { static const mp_obj_fun_builtin_fixed_t get_model_output_dim_obj = { .base = { &mp_type_fun_builtin_1 }, .fun._1 = (mp_fun_1_t)get_model_output_dim, -}; +}; \ No newline at end of file