Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/natmod/deepcraft/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
110 changes: 108 additions & 2 deletions examples/natmod/deepcraft/dc_mp_iface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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

}
}
22 changes: 11 additions & 11 deletions examples/natmod/deepcraft/mp_src.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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,
};
};
Loading