11/* The Clear BSD License
22 *
3- * Copyright (c) 2025 EdgeImpulse Inc.
3+ * Copyright (c) 2026 EdgeImpulse Inc.
44 * All rights reserved.
55 *
66 * Redistribution and use in source and binary forms, with or without
4444#include " model-parameters/model_metadata.h"
4545#include < string>
4646#include " edge-impulse-sdk/classifier/ei_model_types.h"
47- #include " axon_platform .h"
48- #include " axon_driver .h"
49- #include " axon_nn_infer .h"
50- #include " axon_nn_infer_test .h"
51- #include " axon_stringization .h"
47+ #include " axon/nrf_axon_platform .h"
48+ #include " drivers/axon/nrf_axon_driver .h"
49+ #include " drivers/axon/nrf_axon_nn_infer .h"
50+ #include " drivers/axon/nrf_axon_nn_infer_test .h"
51+ #include " axon/nrf_axon_stringization .h"
5252#include < inttypes.h>
5353
54+ #if !defined (NRF_AXON_MODEL_NAME)
55+ #error "NRF_AXON_MODEL_NAME is not defined. Please define NRF_AXON_MODEL_NAME"
56+ #endif
5457
5558/*
5659* Create the model include header file name and structure from
5760* the model name.
5861*/
59- #define AXON_MODEL_FILE_NAME_ROOT axon_model_
60- #define AXON_MODEL_LAYERS_FILE_NAME_ROOT AXON_MODEL_FILE_NAME_ROOT
61- #define AXON_MODEL_TEST_VECTORS_FILE_NAME_ROOT AXON_MODEL_FILE_NAME_ROOT
62- #define AXON_MODEL_TEST_VECTORS_FILE_NAME_END _test_vectors_.h
63- #define AXON_MODEL_LAYERS_FILE_NAME_TAIL _layers_.h
62+ #define AXON_MODEL_FILE_NAME_ROOT nrf_axon_model_
63+
6464#define AXON_MODEL_DOT_H _.h
6565
66- #define AXON_MODEL_FILE_NAME STRINGIZE_3_CONCAT (AXON_MODEL_FILE_NAME_ROOT , AXON_MODEL_NAME , AXON_MODEL_DOT_H )
67- #define AXON_MODEL_FILE_LAYERS_NAME STRINGIZE_3_CONCAT (AXON_MODEL_LAYERS_FILE_NAME_ROOT , AXON_MODEL_NAME , AXON_MODEL_LAYERS_FILE_NAME_TAIL )
68- #define AXON_MODEL_TEST_VECTORS_FILE_NAME STRINGIZE_3_CONCAT (AXON_MODEL_TEST_VECTORS_FILE_NAME_ROOT , AXON_MODEL_NAME , AXON_MODEL_TEST_VECTORS_FILE_NAME_END )
66+ #define AXON_MODEL_FILE_NAME STRINGIZE_3_CONCAT (AXON_MODEL_FILE_NAME_ROOT , NRF_AXON_MODEL_NAME , AXON_MODEL_DOT_H )
6967
70- // // generate structure name model_<model_name>
7168#define THE_REAL_MODEL_STRUCT_NAME (model_name ) model_##model_name
7269#define THE_MODEL_STRUCT_NAME (model_name ) THE_REAL_MODEL_STRUCT_NAME (model_name)
7370
74- // // generate structure name model_<model_name>_layer_list
75- #define THE_REAL_MODEL_LAYERS_STRUCT_NAME (model_name ) model_##model_name##_layer_list
76- #define THE_MODEL_LAYERS_STRUCT_NAME (model_name ) THE_REAL_MODEL_LAYERS_STRUCT_NAME (model_name)
71+ #define NRF_AXON_MODEL_ALLOCATE_PACKED_OUTPUT_BUFFER 1 // model allocates a dedicated buffer for the output.
7772
78- // // generate structure name model_<model_name>_test_vectors
79- #define THE_REAL_MODEL_TEST_VECTORS_STRUCT_NAME (model_name ) model_##model_name##_test_vectors
80- #define THE_MODEL_TEST_VECTORS_STRUCT_NAME (model_name ) THE_REAL_MODEL_TEST_VECTORS_STRUCT_NAME (model_name)
73+ #include AXON_MODEL_FILE_NAME
8174
82- // generate structure name <model_name>_input_test_vectors
83- #define THE_REAL_TEST_INPUT_VECTORS_LIST_NAME (model_name ) model_name##_input_test_vectors
84- #define THE_TEST_INPUT_VECTORS_LIST_NAME (model_name ) THE_REAL_TEST_INPUT_VECTORS_LIST_NAME (model_name)
75+ const nrf_axon_nn_compiled_model_s *nrf_axon_compiled_model;
8576
86- // generate structure name <model_name>_expected_output_test_vectors
87- #define THE_REAL_expected_output_vectors_NAME (model_name ) model_name##_expected_output_vectors
88- #define THE_expected_output_vectors_NAME (model_name ) THE_REAL_expected_output_vectors_NAME(model_name)
77+ static bool nrf_axon_init = false ;
8978
90- // generate structure name <model_name>_layer_vectors
91- #define THE_REAL_layer_vectors_NAME (model_name ) model_name##_layer_expected_output_vectors
92- #define THE_layer_vectors_NAME (model_name ) THE_REAL_layer_vectors_NAME(model_name)
79+ /* *
80+ * This code needs to be run one-time only.
81+ */
82+ int one_time_init ()
83+ {
84+ nrf_axon_platform_init ();
85+ nrf_axon_result_e result_axon = nrf_axon_platform_init ();
9386
94- #include AXON_MODEL_FILE_NAME
87+ if (result_axon != NRF_AXON_RESULT_SUCCESS ){
88+ ei_printf (" ERR: nrf_axon_platform_init failed!\n " );
89+ return EI_IMPULSE_NORDIC_AXON_ERROR ;
90+ }
9591
96- const axon_nn_compiled_model_struct *model_static_info;
92+ nrf_axon_compiled_model = &THE_MODEL_STRUCT_NAME (NRF_AXON_MODEL_NAME );
93+
94+ result_axon = nrf_axon_nn_model_validate (nrf_axon_compiled_model);
95+ if (result_axon != NRF_AXON_RESULT_SUCCESS ){
96+ ei_printf (" ERR: nrf_axon_nn_model_validate failed!\n " );
97+ return EI_IMPULSE_NORDIC_AXON_ERROR ;
98+ }
99+ return EI_IMPULSE_OK ;
100+ }
101+
102+ int one_time_cleanup ()
103+ {
104+ nrf_axon_platform_close ();
105+ return EI_IMPULSE_OK ;
106+ }
107+
108+ /* *
109+ * @brief Performs session initialization
110+ *
111+ * A "session" is when inference is occuring regularlly on continuously streaming input.
112+ * This function is needed for streaming style models that have internal state variables that need
113+ * to be initialized at the start of a session.
114+ */
115+ int streaming_session_init ()
116+ {
117+ return nrf_axon_nn_model_init_vars (nrf_axon_compiled_model);
118+ }
97119
98120EI_IMPULSE_ERROR run_nn_inference (
99121 const ei_impulse_t *impulse,
@@ -114,79 +136,83 @@ EI_IMPULSE_ERROR run_nn_inference(
114136 ei_printf (" INFO: Start Platform!\n " );
115137 }
116138
117- AxonResultEnum result_axon = axon_platform_init ();
118-
119- if (result_axon != kAxonResultSuccess ) {
120- ei_printf ( " ERR: axon_platform_init failed! \n " );
121- return EI_IMPULSE_INFERENCE_ERROR ;
139+ if (nrf_axon_init == false ) {
140+ if ( one_time_init () != EI_IMPULSE_OK ) {
141+ return EI_IMPULSE_NORDIC_AXON_ERROR ;
142+ }
143+ nrf_axon_init = true ;
122144 }
123- void *axon_handle = axon_driver_get_handle ();
124145
125146 if (debug) {
126147 ei_printf (" INFO: Prepare and run Axon!\n " );
127148 }
128149
129- axon_nn_model_inference_wrapper_struct model_wrapper;
130- model_static_info = &THE_MODEL_STRUCT_NAME (AXON_MODEL_NAME );
131-
132- int init_result = axon_nn_model_init (&model_wrapper, model_static_info);
133- if (init_result != 0 ) {
134- ei_printf (" ERR: axon_nn_model_init failed: %d\n " , init_result);
135- return EI_IMPULSE_INFERENCE_ERROR ;
136- }
137-
138- axon_nn_model_init_vars (&model_wrapper);
139-
140- uint32_t vector_size = model_static_info->inputs [0 ].dimensions .height * model_static_info->inputs [0 ].dimensions .width ;
150+ uint32_t vector_size = nrf_axon_compiled_model->inputs [nrf_axon_compiled_model->external_input_ndx ].dimensions .height * nrf_axon_compiled_model->inputs [nrf_axon_compiled_model->external_input_ndx ].dimensions .width ;
141151 int8_t input_vector[vector_size];
142152
143- uint64_t ctx_start_us = ei_read_timer_us ();
144-
145153 // copy rescale the input features to int8 and copy to input buffer
154+ /* *
155+ * @FIXME!!! CHECK IF MODEL IS TRANSPOSED! THIS INFO IS NOT STORED IN THE MODEL, BUT SHOULD BE!
156+ */
146157 for (size_t i = 0 ; i < matrix->rows * matrix->cols ; i++) {
147158 // TODO: get scale and zero point from the model
148159 input_vector[i] = (int8_t )((matrix->buffer [i] / graph_config->input_scale ) + graph_config->input_zeropoint );
149160 }
150161
151- AxonResultEnum result_axon_infer = axon_nn_model_infer_sync (
152- axon_handle, // your hardware handle
153- model_static_info,
154- &model_wrapper. cmd_buf_info ,
162+ uint64_t ctx_start_us = ei_read_timer_us ();
163+
164+ nrf_axon_result_e result_axon_infer = nrf_axon_nn_model_infer_sync (
165+ nrf_axon_compiled_model ,
155166 input_vector,
156- vector_size
157- );
167+ nrf_axon_compiled_model->packed_output_buf );
158168
159- if (result_axon_infer != kAxonResultSuccess ) {
160- printf (" ERR: Inference failed!\n " );
169+ if (result_axon_infer != NRF_AXON_RESULT_SUCCESS ) {
170+ ei_printf (" ERR: Inference failed!\n " );
161171 return EI_IMPULSE_INFERENCE_ERROR ;
162172 }
163173
164174 // ei_sleep(3); // let the axon finish processing
165175 result->timing .classification_us = (int64_t )(ei_read_timer_us () - ctx_start_us);
176+ result->timing .classification = (int )(result->timing .classification_us / 1000 );
166177
178+ #if 0
179+ /**
180+ * get_classification isn't necessary to validate the result. It only applies to single
181+ * channel classification networks
182+ */
167183 const char *label;
168184 int32_t score;
169185 int16_t class_idx = axon_nn_get_classification(&model_wrapper, NULL, &label, &score, NULL);
170186
171187 if (class_idx >= 0) {
172188 if (debug) {
173189 ei_printf("INFO: Axon inference successful.\n");
174- printf (" INFO: Predicted class: %d\n " , class_idx);
175- printf (" INFO: Label: %s\n " , label);
176- printf (" INFO: Score: %d\n " , score);
190+ ei_printf ("INFO: Predicted class: %d\n", class_idx);
191+ ei_printf ("INFO: Label: %s\n", label);
192+ ei_printf ("INFO: Score: %d\n", score);
177193 }
178194 // here channel is always one and byte width is always 1 for quantized models
179- uint32_t output_size = model_static_info ->output_dimensions .height * model_static_info ->output_dimensions .width ;
195+ uint32_t output_size = nrf_axon_compiled_model ->output_dimensions.height * nrf_axon_compiled_model ->output_dimensions.width * nrf_axon_compiled_model->output_dimensions.channel_cnt ;
180196 result->_raw_outputs[learn_block_index + 0].matrix_i8 = new matrix_i8_t(1, output_size);
181- memcpy (result->_raw_outputs [learn_block_index + 0 ].matrix_i8 ->buffer , (int8_t *)model_static_info-> output_ptr , output_size * sizeof (int8_t ));
197+ memcpy(result->_raw_outputs[learn_block_index + 0].matrix_i8->buffer, (int8_t *)nrf_axon_compiled_model->packed_output_buf , output_size * sizeof(int8_t));
182198 result->_raw_outputs[learn_block_index].blockId = block_config->block_id;
183199 } else {
184- printf (" ERR: axon Classification failed!\n " );
200+ ei_printf ("ERR: axon Classification failed!\n");
185201 return EI_IMPULSE_INFERENCE_ERROR;
186202 }
187-
188- axon_platform_close ();
189-
203+ #else
204+ // here channel is always one and byte width is always 1 for quantized models
205+ /* *
206+ * Not sure how output channels will always be 1... fomo models have multiple output channels.
207+ * Also, is new matrix_i8_t(1, output_size) a memory leak? Or does it get freed elsewhere?
208+ * If the matrix_i8 was pre-allocated, it could be passed to the infer function directly, saving an extra memcpy.
209+ */
210+ uint32_t output_size = nrf_axon_compiled_model->output_dimensions .height * nrf_axon_compiled_model->output_dimensions .width * nrf_axon_compiled_model->output_dimensions .channel_cnt ;
211+ result->_raw_outputs [learn_block_index + 0 ].matrix_i8 = new matrix_i8_t (1 , output_size);
212+ memcpy (result->_raw_outputs [learn_block_index + 0 ].matrix_i8 ->buffer , (int8_t *)nrf_axon_compiled_model->packed_output_buf , output_size * sizeof (int8_t ));
213+ result->_raw_outputs [learn_block_index].blockId = block_config->block_id ;
214+ #endif
215+
190216 return EI_IMPULSE_OK ;
191217}
192218
0 commit comments