diff --git a/python/tflite_micro/python_ops_resolver.cc b/python/tflite_micro/python_ops_resolver.cc index 587adf7a..8d508e1c 100644 --- a/python/tflite_micro/python_ops_resolver.cc +++ b/python/tflite_micro/python_ops_resolver.cc @@ -53,6 +53,7 @@ PythonOpsResolver::PythonOpsResolver() { AddFloor(); AddFloorDiv(); AddFloorMod(); + AddFramer(); AddFullyConnected(); AddGather(); AddGatherNd(); diff --git a/signal/micro/kernels/BUILD b/signal/micro/kernels/BUILD index 7f348dc9..040332c6 100644 --- a/signal/micro/kernels/BUILD +++ b/signal/micro/kernels/BUILD @@ -3,13 +3,12 @@ load( "micro_copts", ) -package( - licenses = ["notice"], -) +package(licenses = ["notice"]) cc_library( name = "register_signal_ops", srcs = [ + "framer.cc", "rfft.cc", "window.cc", ], @@ -21,6 +20,7 @@ cc_library( "//tensorflow/lite/micro", ], deps = [ + "//signal/src:circular_buffer", "//signal/src:rfft", "//signal/src:window", "//tensorflow/lite:type_to_tflitetype", @@ -87,3 +87,29 @@ cc_test( "//tensorflow/lite/micro/testing:micro_test", ], ) + +cc_library( + name = "framer_flexbuffers_generated_data", + srcs = [ + "framer_flexbuffers_generated_data.cc", + ], + hdrs = [ + "framer_flexbuffers_generated_data.h", + ], +) + +cc_test( + name = "framer_test", + srcs = [ + "framer_test.cc", + ], + deps = [ + ":framer_flexbuffers_generated_data", + ":register_signal_ops", + "//tensorflow/lite/c:common", + "//tensorflow/lite/micro:op_resolvers", + "//tensorflow/lite/micro:test_helpers", + "//tensorflow/lite/micro/kernels:kernel_runner", + "//tensorflow/lite/micro/testing:micro_test", + ], +) diff --git a/signal/micro/kernels/framer.cc b/signal/micro/kernels/framer.cc new file mode 100644 index 00000000..8437bd06 --- /dev/null +++ b/signal/micro/kernels/framer.cc @@ -0,0 +1,199 @@ +/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include + +#include "signal/src/circular_buffer.h" +#include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/micro/flatbuffer_utils.h" +#include "tensorflow/lite/micro/kernels/kernel_util.h" +#include "tensorflow/lite/micro/memory_helpers.h" +#include "tensorflow/lite/micro/micro_utils.h" + +namespace tflite { +namespace { + +constexpr int kInputTensor = 0; +constexpr int kOutputTensor = 0; +constexpr int kOutputValidTensor = 1; + +// Indices into the init flexbuffer's vector. +// The parameter's name is in the comment that follows. +// Elements in the vectors are ordered alphabetically by parameter name. +constexpr int kFrameSizeIndex = 0; // 'frame_size' +constexpr int kFrameStepIndex = 1; // 'frame_step' +constexpr int kPrefillIndex = 2; // 'prefill' + +struct TFLMSignalFramerParams { + int32_t frame_size; + int32_t frame_step; + int32_t outer_dims; + int32_t n_frames; + bool prefill; + + int8_t** state_buffers; + tflite::tflm_signal::CircularBuffer** circular_buffers; +}; + +void ResetState(TFLMSignalFramerParams* params) { + for (int i = 0; i < params->outer_dims; ++i) { + tflite::tflm_signal::CircularBufferReset(params->circular_buffers[i]); + if (params->prefill) { + tflite::tflm_signal::CircularBufferWriteZeros( + params->circular_buffers[i], params->frame_size - params->frame_step); + } + } +} + +void* Init(TfLiteContext* context, const char* buffer, size_t length) { + const uint8_t* buffer_t = reinterpret_cast(buffer); + + auto* params = + static_cast(context->AllocatePersistentBuffer( + context, sizeof(TFLMSignalFramerParams))); + + if (params == nullptr) { + return nullptr; + } + + tflite::FlexbufferWrapper fbw(buffer_t, length); + params->frame_size = fbw.ElementAsInt32(kFrameSizeIndex); + params->frame_step = fbw.ElementAsInt32(kFrameStepIndex); + params->prefill = fbw.ElementAsBool(kPrefillIndex); + return params; +} + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE_EQ(context, NumInputs(node), 1); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 2); + + MicroContext* micro_context = GetMicroContext(context); + + TfLiteTensor* input = + micro_context->AllocateTempInputTensor(node, kInputTensor); + TF_LITE_ENSURE(context, input != nullptr); + TfLiteTensor* output = + micro_context->AllocateTempOutputTensor(node, kOutputTensor); + TF_LITE_ENSURE(context, output != nullptr); + TfLiteTensor* output_valid = + micro_context->AllocateTempOutputTensor(node, kOutputValidTensor); + TF_LITE_ENSURE(context, output_valid != nullptr); + + TF_LITE_ENSURE_EQ(context, NumDimensions(input) + 1, NumDimensions(output)); + TF_LITE_ENSURE_EQ(context, NumDimensions(output_valid), 0); + + TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteInt16); + TF_LITE_ENSURE_TYPES_EQ(context, output->type, kTfLiteInt16); + TF_LITE_ENSURE_TYPES_EQ(context, output_valid->type, kTfLiteBool); + + auto* params = reinterpret_cast(node->user_data); + + RuntimeShape input_shape = GetTensorShape(input); + int innermost_dim = input_shape.Dims(input_shape.DimensionsCount() - 1); + TF_LITE_ENSURE(context, innermost_dim >= params->frame_step); + TF_LITE_ENSURE_EQ(context, innermost_dim % params->frame_step, 0); + params->outer_dims = input_shape.FlatSize() / innermost_dim; + params->n_frames = innermost_dim / params->frame_step; + + params->state_buffers = + static_cast(context->AllocatePersistentBuffer( + context, params->outer_dims * sizeof(int8_t*))); + params->circular_buffers = static_cast( + context->AllocatePersistentBuffer( + context, + params->outer_dims * sizeof(tflite::tflm_signal::CircularBuffer*))); + for (int i = 0; i < params->outer_dims; i++) { + // Calculate the capacity of the circular buffer. Round up the frame size to + // a multiple of frame step. Saves memory relative to the simpler frame_size + // + frame_step. For example: step_size = 160, frame_size = 400 capacity = + // 480 vs. step_size + frame_size = 560 + size_t capacity = (params->frame_size + params->frame_step - 1) / + params->frame_step * params->frame_step; + + size_t state_size = + tflite::tflm_signal::CircularBufferGetNeededMemory(capacity); + params->state_buffers[i] = + static_cast(context->AllocatePersistentBuffer( + context, state_size * sizeof(int8_t))); + params->circular_buffers[i] = tflite::tflm_signal::CircularBufferInit( + capacity, params->state_buffers[i], state_size); + } + + ResetState(params); + + micro_context->DeallocateTempTfLiteTensor(input); + micro_context->DeallocateTempTfLiteTensor(output); + micro_context->DeallocateTempTfLiteTensor(output_valid); + + return kTfLiteOk; +} + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + auto* params = reinterpret_cast(node->user_data); + + const TfLiteEvalTensor* input = + tflite::micro::GetEvalInput(context, node, kInputTensor); + TfLiteEvalTensor* output = + tflite::micro::GetEvalOutput(context, node, kOutputTensor); + TfLiteEvalTensor* output_valid = + tflite::micro::GetEvalOutput(context, node, kOutputValidTensor); + + const int16_t* input_data = tflite::micro::GetTensorData(input); + int16_t* output_data = tflite::micro::GetTensorData(output); + bool* output_valid_data = tflite::micro::GetTensorData(output_valid); + *output_valid_data = true; + + for (int i = 0; i < params->outer_dims; i++) { + for (int frame = 0; frame < params->n_frames; frame++) { + int input_idx = (i * params->n_frames + frame) * params->frame_step; + int output_idx = (i * params->n_frames + frame) * params->frame_size; + tflite::tflm_signal::CircularBufferWrite(params->circular_buffers[i], + &input_data[input_idx], + params->frame_step); + + if (tflite::tflm_signal::CircularBufferAvailable( + params->circular_buffers[i]) >= + static_cast(params->frame_size)) { + tflite::tflm_signal::CircularBufferGet(params->circular_buffers[i], + params->frame_size, + &output_data[output_idx]); + tflite::tflm_signal::CircularBufferDiscard(params->circular_buffers[i], + params->frame_step); + } else { + *output_valid_data = false; + } + } + } + + return kTfLiteOk; +} + +void Reset(TfLiteContext* context, void* buffer) { + ResetState(static_cast(buffer)); +} + +} // namespace + +namespace tflm_signal { +// TODO(b/286250473): remove namespace once de-duped libraries above +TFLMRegistration* Register_FRAMER() { + static TFLMRegistration r = + tflite::micro::RegisterOp(Init, Prepare, Eval, nullptr, Reset); + return &r; +} +} // namespace tflm_signal + +} // namespace tflite diff --git a/signal/micro/kernels/framer_flexbuffers_generated_data.cc b/signal/micro/kernels/framer_flexbuffers_generated_data.cc new file mode 100644 index 00000000..53d08109 --- /dev/null +++ b/signal/micro/kernels/framer_flexbuffers_generated_data.cc @@ -0,0 +1,34 @@ +/* Copyright 2021 The TensorFlow Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +// This file is generated. See: +// tensorflow/lite/micro/kernels/test_data_generation/README.md + +#include "signal/micro/kernels/framer_flexbuffers_generated_data.h" + +const int g_gen_data_size_3_1_0_framer = 46; +const unsigned char g_gen_data_3_1_0_framer[] = { + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x00, 0x70, 0x72, + 0x65, 0x66, 0x69, 0x6c, 0x6c, 0x00, 0x03, 0x1f, 0x15, 0x0b, 0x03, 0x01, + 0x03, 0x03, 0x01, 0x00, 0x04, 0x04, 0x68, 0x06, 0x24, 0x01, +}; + +const int g_gen_data_size_5_2_1_framer = 46; +const unsigned char g_gen_data_5_2_1_framer[] = { + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x66, + 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x00, 0x70, 0x72, + 0x65, 0x66, 0x69, 0x6c, 0x6c, 0x00, 0x03, 0x1f, 0x15, 0x0b, 0x03, 0x01, + 0x03, 0x05, 0x02, 0x01, 0x04, 0x04, 0x68, 0x06, 0x24, 0x01, +}; diff --git a/signal/micro/kernels/framer_flexbuffers_generated_data.h b/signal/micro/kernels/framer_flexbuffers_generated_data.h new file mode 100644 index 00000000..655bfa6a --- /dev/null +++ b/signal/micro/kernels/framer_flexbuffers_generated_data.h @@ -0,0 +1,25 @@ +/* Copyright 2021 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef SIGNAL_MICRO_KERNELS_TEST_DATA_GENERATION_GENERATE_FRAMER_FLEXBUFFERS_DATA_H_ +#define SIGNAL_MICRO_KERNELS_TEST_DATA_GENERATION_GENERATE_FRAMER_FLEXBUFFERS_DATA_H_ + +extern const int g_gen_data_size_3_1_0_framer; +extern const unsigned char g_gen_data_3_1_0_framer[]; + +extern const int g_gen_data_size_5_2_1_framer; +extern const unsigned char g_gen_data_5_2_1_framer[]; + +#endif // SIGNAL_MICRO_KERNELS_TEST_DATA_GENERATION_GENERATE_FRAMER_FLEXBUFFERS_DATA_H_ diff --git a/signal/micro/kernels/framer_test.cc b/signal/micro/kernels/framer_test.cc new file mode 100644 index 00000000..f6cc3a03 --- /dev/null +++ b/signal/micro/kernels/framer_test.cc @@ -0,0 +1,250 @@ +/* Copyright 2021 The TensorFlow Authors. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include + +#include "signal/micro/kernels/framer_flexbuffers_generated_data.h" +#include "tensorflow/lite/micro/kernels/kernel_runner.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" + +namespace tflite { +namespace { + +constexpr int kFrameSizeIndex = 0; // 'frame_size' +constexpr int kFrameStepIndex = 1; // 'frame_step' +constexpr int kPrefillIndex = 2; // 'prefill' +constexpr int kInputsSize = 1; +constexpr int kOutputsSize = 2; +constexpr int kTensorsSize = kInputsSize + kOutputsSize; + +class FramerKernelRunner { + public: + FramerKernelRunner(int* input_dims_data, int16_t* input_data, + int* output_dims_data, int16_t* output_data, + int* output_ready_dims_data, bool* output_ready) + : inputs_array_{testing::IntArrayFromInts(inputs_array_data_)}, + outputs_array_{testing::IntArrayFromInts(outputs_array_data_)} { + tensors_[0] = testing::CreateTensor( + input_data, testing::IntArrayFromInts(input_dims_data)); + + tensors_[1] = testing::CreateTensor( + output_data, testing::IntArrayFromInts(output_dims_data)); + + tensors_[2] = testing::CreateTensor( + output_ready, testing::IntArrayFromInts(output_ready_dims_data)); + + // go/tflm-static-cleanups for reasoning new is being used like this + kernel_runner_ = new (kernel_runner_buffer) micro::KernelRunner( + *registration_, tensors_, kTensorsSize, inputs_array_, outputs_array_, + /*builtin_data=*/nullptr); + } + + micro::KernelRunner& kernel_runner() { return *kernel_runner_; } + + private: + uint8_t kernel_runner_buffer[sizeof(micro::KernelRunner)]; + int inputs_array_data_[kInputsSize + 1] = {kInputsSize, 0}; + int outputs_array_data_[kOutputsSize + 1] = {kOutputsSize, 1, 2}; + TfLiteTensor tensors_[kTensorsSize] = {}; + TfLiteIntArray* inputs_array_ = nullptr; + TfLiteIntArray* outputs_array_ = nullptr; + TFLMRegistration* registration_ = tflm_signal::Register_FRAMER(); + micro::KernelRunner* kernel_runner_ = nullptr; +}; + +alignas(alignof(FramerKernelRunner)) uint8_t + framer_kernel_runner_buffer[sizeof(FramerKernelRunner)]; + +void TestFramerInvoke(int* input_dims_data, int16_t* input_data, + int* output_dims_data, const int16_t* golden, + int golden_len, int* output_ready_dims_data, + const unsigned char* flexbuffers_data, + const unsigned int flexbuffers_data_size, + int16_t* output_data, bool* output_ready, + micro::KernelRunner* runner) { + FlexbufferWrapper fbw(flexbuffers_data, flexbuffers_data_size); + int frame_size = fbw.ElementAsInt32(kFrameSizeIndex); + int frame_step = fbw.ElementAsInt32(kFrameStepIndex); + bool prefill = fbw.ElementAsBool(kPrefillIndex); + int latency_samples = frame_size - frame_step; + int input_size = input_dims_data[input_dims_data[0]]; + int outer_dims = 1; + for (int i = 1; i < input_dims_data[0]; i++) { + outer_dims *= input_dims_data[i]; + } + int n_frames = output_dims_data[output_dims_data[0] - 1]; + TF_LITE_MICRO_EXPECT_EQ(frame_size, output_dims_data[output_dims_data[0]]); + for (int i = 0; i < golden_len - latency_samples; i += input_size) { + for (int outer_dim = 0; outer_dim < outer_dims; outer_dim++) { + memcpy(&input_data[outer_dim * input_size], &golden[latency_samples + i], + input_size * sizeof(int16_t)); + } + TF_LITE_MICRO_EXPECT_EQ(runner->Invoke(), kTfLiteOk); + TF_LITE_MICRO_EXPECT_EQ(*output_ready, (i >= latency_samples) || prefill); + if (*output_ready == true) { + for (int outer_dim = 0; outer_dim < outer_dims; outer_dim++) { + for (int frame = 0; frame < n_frames; frame++) { + int output_idx = + outer_dim * frame_size * n_frames + frame * frame_size; + int golden_idx = i + frame * frame_step; + TF_LITE_MICRO_EXPECT_EQ( + 0, memcmp(&golden[golden_idx], &output_data[output_idx], + frame_size * sizeof(int16_t))); + } + } + } + } +} + +void TestFramer(int* input_dims_data, int16_t* input_data, + int* output_dims_data, const int16_t* golden, int golden_len, + int* output_ready_dims_data, + const unsigned char* flexbuffers_data, + const unsigned int flexbuffers_data_size, + int16_t* output_data) { + bool output_ready = false; + FramerKernelRunner* framer_runner = new (framer_kernel_runner_buffer) + FramerKernelRunner(input_dims_data, input_data, output_dims_data, + output_data, output_ready_dims_data, &output_ready); + // TfLite uses a char* for the raw bytes whereas flexbuffers use an unsigned + // char*. This small discrepancy results in compiler warnings unless we + // reinterpret_cast right before passing in the flexbuffer bytes to the + // KernelRunner. + TF_LITE_MICRO_EXPECT_EQ(framer_runner->kernel_runner().InitAndPrepare( + reinterpret_cast(flexbuffers_data), + flexbuffers_data_size), + kTfLiteOk); + TestFramerInvoke(input_dims_data, input_data, output_dims_data, golden, + golden_len, output_ready_dims_data, flexbuffers_data, + flexbuffers_data_size, output_data, &output_ready, + &framer_runner->kernel_runner()); +} + +void TestFramerReset(int* input_dims_data, int16_t* input_data, + int* output_dims_data, const int16_t* golden, + int golden_len, int* output_ready_dims_data, + const unsigned char* flexbuffers_data, + const unsigned int flexbuffers_data_size, + int16_t* output_data) { + bool output_ready = false; + FramerKernelRunner* framer_runner = new (framer_kernel_runner_buffer) + FramerKernelRunner(input_dims_data, input_data, output_dims_data, + output_data, output_ready_dims_data, &output_ready); + // TfLite uses a char* for the raw bytes whereas flexbuffers use an unsigned + // char*. This small discrepancy results in compiler warnings unless we + // reinterpret_cast right before passing in the flexbuffer bytes to the + // KernelRunner. + TF_LITE_MICRO_EXPECT_EQ(framer_runner->kernel_runner().InitAndPrepare( + reinterpret_cast(flexbuffers_data), + flexbuffers_data_size), + kTfLiteOk); + TestFramerInvoke(input_dims_data, input_data, output_dims_data, golden, + golden_len, output_ready_dims_data, flexbuffers_data, + flexbuffers_data_size, output_data, &output_ready, + &framer_runner->kernel_runner()); + framer_runner->kernel_runner().Reset(); + TestFramerInvoke(input_dims_data, input_data, output_dims_data, golden, + golden_len, output_ready_dims_data, flexbuffers_data, + flexbuffers_data_size, output_data, &output_ready, + &framer_runner->kernel_runner()); +} + +} // namespace +} // namespace tflite + +TF_LITE_MICRO_TESTS_BEGIN + +TF_LITE_MICRO_TEST(FramerTest_3_1_0) { + const int kInputSize = 1; + const int kOutputSize = 3; + int input_dims_data[] = {1, kInputSize}; + int output_dims_data[] = {2, 1, kOutputSize}; + int output_ready_dims_data[] = {0}; + const int16_t golden[] = {0x0, 0x0, 0x1234, 0x5678, 0x4321, 0x7777}; + int16_t input_data; + int16_t output_data[kOutputSize]; + + tflite::TestFramer(input_dims_data, &input_data, output_dims_data, golden, + sizeof(golden) / sizeof(int16_t), output_ready_dims_data, + g_gen_data_3_1_0_framer, g_gen_data_size_3_1_0_framer, + output_data); +} + +TF_LITE_MICRO_TEST(FramerTest_5_2_1) { + const int kInputSize = 2; + const int kOutputSize = 5; + int input_dims_data[] = {1, kInputSize}; + int output_dims_data[] = {2, 1, kOutputSize}; + int output_ready_dims_data[] = {0}; + const int16_t golden[] = {0x0, 0x0, 0x0, 0x1010, 0x0202, 0x7070, 0x0606}; + + int16_t input_data[kInputSize]; + int16_t output_data[kOutputSize]; + + tflite::TestFramer(input_dims_data, input_data, output_dims_data, golden, + sizeof(golden) / sizeof(int16_t), output_ready_dims_data, + g_gen_data_5_2_1_framer, g_gen_data_size_5_2_1_framer, + output_data); +} + +TF_LITE_MICRO_TEST(FramerTest_5_2_1_NFrames2) { + const int kInputSize = 4; + const int kOutputSize = 5; + const int kNFrames = 2; + int input_dims_data[] = {1, kInputSize}; + int output_dims_data[] = {2, kNFrames, kOutputSize}; + int output_ready_dims_data[] = {0}; + const int16_t golden[] = {0x0, 0x0, 0x0, 0x1010, 0x0202, 0x7070, 0x0606}; + + int16_t input_data[kInputSize]; + int16_t output_data[kNFrames * kOutputSize]; + + tflite::TestFramer(input_dims_data, input_data, output_dims_data, golden, + sizeof(golden) / sizeof(int16_t), output_ready_dims_data, + g_gen_data_5_2_1_framer, g_gen_data_size_5_2_1_framer, + output_data); +} + +TF_LITE_MICRO_TEST(FramerTest_5_2_1_NFrames2OuterDims4) { + const int kInputSize = 4; + const int kOutputSize = 5; + int input_dims_data[] = {3, 2, 2, kInputSize}; + int output_dims_data[] = {4, 2, 2, 2, kOutputSize}; + int output_ready_dims_data[] = {0}; + const int16_t golden[] = {0x0, 0x0, 0x0, 0x1010, 0x0202, 0x7070, 0x0606}; + + int16_t input_data[2 * 2 * kInputSize]; + int16_t output_data[2 * 2 * 2 * kOutputSize]; + + tflite::TestFramer(input_dims_data, input_data, output_dims_data, golden, + sizeof(golden) / sizeof(int16_t), output_ready_dims_data, + g_gen_data_5_2_1_framer, g_gen_data_size_5_2_1_framer, + output_data); +} + +TF_LITE_MICRO_TEST(TestReset) { + const int kInputSize = 1; + const int kOutputSize = 3; + int input_dims_data[] = {1, kInputSize}; + int output_dims_data[] = {2, 1, kOutputSize}; + int output_ready_dims_data[] = {0}; + const int16_t golden[] = {0x0, 0x0, 0x1234, 0x5678, 0x4321, 0x7777}; + int16_t input_data; + int16_t output_data[kOutputSize]; + tflite::TestFramerReset(input_dims_data, &input_data, output_dims_data, + golden, sizeof(golden) / sizeof(int16_t), + output_ready_dims_data, g_gen_data_3_1_0_framer, + g_gen_data_size_3_1_0_framer, output_data); +} + +TF_LITE_MICRO_TESTS_END diff --git a/signal/src/BUILD b/signal/src/BUILD index 9b5e4c6d..c5e51fb2 100644 --- a/signal/src/BUILD +++ b/signal/src/BUILD @@ -3,6 +3,12 @@ package( licenses = ["notice"], ) +cc_library( + name = "circular_buffer", + srcs = ["circular_buffer.cc"], + hdrs = ["circular_buffer.h"], +) + cc_library( name = "complex", hdrs = ["complex.h"], diff --git a/signal/src/circular_buffer.cc b/signal/src/circular_buffer.cc new file mode 100644 index 00000000..7638d912 --- /dev/null +++ b/signal/src/circular_buffer.cc @@ -0,0 +1,290 @@ +/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "signal/src/circular_buffer.h" + +#include +#include +#include + +#define ASSERT assert + +namespace tflite { +namespace tflm_signal { +// TODO(b/286250473): remove namespace once de-duped libraries above +void CircularBufferReset(tflm_signal::CircularBuffer* cb) { + cb->read = 0; + cb->write = 0; + cb->empty = 1; + cb->buffer = (int16_t*)(cb + 1); + memset(cb->buffer, 0, sizeof(cb->buffer[0]) * cb->buffer_size); +} + +size_t CircularBufferGetNeededMemory(size_t capacity) { + return sizeof(CircularBuffer) + sizeof(int16_t) * 2 * capacity; +} + +CircularBuffer* CircularBufferInit(size_t capacity, void* state, + size_t state_size) { + ASSERT(CircularBufferGetNeededMemory(capacity) >= state_size); + CircularBuffer* cb = (CircularBuffer*)state; + cb->buffer_size = 2 * capacity; + cb->capacity = capacity; + CircularBufferReset(cb); + return cb; +} + +size_t CircularBufferCapacity(const tflm_signal::CircularBuffer* cb) { + return cb->capacity; +} + +bool CircularBufferFull(const tflm_signal::CircularBuffer* cb) { + return cb->read == cb->write && cb->empty == 0; +} + +bool CircularBufferEmpty(const tflm_signal::CircularBuffer* cb) { + return cb->empty == 1; +} + +size_t CircularBufferAvailable(const tflm_signal::CircularBuffer* cb) { + const int32_t diff = cb->write - cb->read; + if (diff > 0) { + return diff; + } else if (diff < 0) { + return cb->capacity + diff; + } else if (cb->empty == 1) { + return 0; + } else { + return cb->capacity; + } +} + +size_t CircularBufferCanWrite(const tflm_signal::CircularBuffer* cb) { + return cb->capacity - CircularBufferAvailable(cb); +} + +void CircularBufferAdd(tflm_signal::CircularBuffer* cb, int16_t value) { + ASSERT(!CircularBufferFull(cb)); + cb->buffer[cb->write] = value; + cb->buffer[cb->write + cb->capacity] = value; + if (++cb->write == cb->capacity) { + cb->write = 0; + } + cb->empty = 0; +} + +void CircularBufferWrite(tflm_signal::CircularBuffer* cb, const int16_t* values, + size_t n) { + if (n > 0) { + ASSERT(CircularBufferCanWrite(cb) >= n); + size_t write = cb->write; + int16_t* buffer = cb->buffer; + const size_t capacity = cb->capacity; + const size_t end = write + n; + + memcpy(buffer + write, values, n * sizeof(int16_t)); + if (end < capacity) { + memcpy(buffer + capacity + write, values, n * sizeof(int16_t)); + write += n; + } else { + const size_t n1 = capacity - write; + const size_t nbytes1 = n1 * sizeof(int16_t); + memcpy(buffer + capacity + write, values, nbytes1); + const size_t n2 = end - capacity; + if (n2 > 0) { + const size_t nbytes2 = n2 * sizeof(int16_t); + memcpy(buffer, values + n1, nbytes2); + } + write = n2; + } + cb->write = write; + cb->empty = 0; + } +} + +void CircularBufferWriteZeros(tflm_signal::CircularBuffer* cb, size_t n) { + if (n > 0) { + ASSERT(CircularBufferCanWrite(cb) >= n); + size_t write = cb->write; + int16_t* buffer = cb->buffer; + const size_t capacity = cb->capacity; + const size_t end = write + n; + + memset(buffer + write, 0, n * sizeof(int16_t)); + if (end < capacity) { + memset(buffer + capacity + write, 0, n * sizeof(int16_t)); + write += n; + } else { + const size_t n1 = capacity - write; + const size_t nbytes1 = n1 * sizeof(int16_t); + memset(buffer + capacity + write, 0, nbytes1); + const size_t n2 = end - capacity; + if (n2 > 0) { + const size_t nbytes2 = n2 * sizeof(int16_t); + memset(buffer, 0, nbytes2); + } + write = n2; + } + cb->write = write; + cb->empty = 0; + } +} + +int16_t* CircularBufferReserveForWrite(tflm_signal::CircularBuffer* cb, + size_t n) { + ASSERT(cb->write + n <= cb->capacity); + int16_t* write_ptr = cb->buffer + cb->write; + cb->write += n; + if (cb->write == cb->capacity) { + cb->write = 0; + } + cb->empty = cb->empty && n == 0; + return write_ptr; +} + +void CircularBufferExtend(tflm_signal::CircularBuffer* cb, size_t count, + int32_t n) { + if (n > 0 && count > 0) { + ASSERT(CircularBufferCanWrite(cb) >= count * n); + ASSERT(CircularBufferAvailable(cb) >= count); + const size_t capacity = cb->capacity; + // start pos of region to copy + const size_t start = + (count > cb->write) ? cb->write + capacity - count : cb->write - count; + const size_t end = start + count; + int i; + if (end <= capacity) { + // the source elements are contiguous + for (i = 0; i < n; ++i) { + CircularBufferWrite(cb, cb->buffer + start, count); + } + } else { + // the source elements wrap around the end of the buffer + for (i = 0; i < n; ++i) { + const size_t n1 = capacity - start; + const size_t n2 = count - n1; + CircularBufferWrite(cb, cb->buffer + start, n1); + CircularBufferWrite(cb, cb->buffer, n2); + } + } + } + // Note: no need to update empty flag +} + +int16_t CircularBufferRemove(tflm_signal::CircularBuffer* cb) { + ASSERT(!CircularBufferEmpty(cb)); + const int16_t result = cb->buffer[cb->read]; + if (++cb->read == cb->capacity) { + cb->read = 0; + } + if (cb->read == cb->write) { + cb->empty = 1; + } + return result; +} + +int16_t CircularBufferPeek(const tflm_signal::CircularBuffer* cb, + size_t index) { + ASSERT(CircularBufferAvailable(cb) > index); + size_t target = cb->read + index; + while (target >= cb->capacity) { + target -= cb->capacity; + } + return cb->buffer[target]; +} + +void CircularBufferRewind(tflm_signal::CircularBuffer* cb, size_t n) { + ASSERT(n <= CircularBufferCanWrite(cb)); + if (n > cb->read) { + // Must add before subtracting because types are unsigned. + cb->read = (cb->read + cb->capacity) - n; + } else { + cb->read -= n; + } + if (n > 0) cb->empty = 0; +} + +const int16_t* CircularBufferPeekDirect(const tflm_signal::CircularBuffer* cb, + size_t index) { + ASSERT(CircularBufferAvailable(cb) > index); + size_t target = cb->read + index; + while (target >= cb->capacity) { + target -= cb->capacity; + } + return cb->buffer + target; +} + +const int16_t* CircularBufferPeekMax(const tflm_signal::CircularBuffer* cb, + size_t* n) { + if (CircularBufferAvailable(cb) > 0) { + *n = (cb->write <= cb->read) ? cb->capacity - cb->read + : cb->write - cb->read; + return cb->buffer + cb->read; + } else { + *n = 0; + return NULL; + } +} + +void CircularBufferGet(tflm_signal::CircularBuffer* cb, size_t n, + int16_t* values) { + ASSERT(CircularBufferAvailable(cb) >= n); + const int16_t* buffer = cb->buffer; + const size_t read = cb->read; + const size_t end = read + n; + const size_t capacity = cb->capacity; + if (end <= capacity) { + memcpy(values, buffer + read, n * sizeof(int16_t)); + } else { + const size_t n1 = capacity - read; + const size_t n2 = end - capacity; + const size_t nbytes1 = n1 * sizeof(int16_t); + const size_t nbytes2 = n2 * sizeof(int16_t); + memcpy(values, buffer + read, nbytes1); + memcpy(values + n1, buffer, nbytes2); + } +} + +void CircularBufferDiscard(tflm_signal::CircularBuffer* cb, size_t n) { + ASSERT(n > 0); + ASSERT(CircularBufferAvailable(cb) >= n); + cb->read += n; + if (cb->read >= cb->capacity) { + cb->read -= cb->capacity; + } + if (cb->read == cb->write) { + cb->empty = 1; + } +} + +void CircularBufferShift(tflm_signal::CircularBuffer* cb, int n) { + if (n < 0) { + ASSERT(-n <= (int)cb->capacity); + if ((int)cb->read < -n) { + // First add then subtract to ensure positivity as types are unsigned. + cb->read += cb->capacity; + } + cb->read += n; + } else { + ASSERT(n <= (int)cb->capacity); + cb->read += n; + if (cb->read >= cb->capacity) { + cb->read -= cb->capacity; + } + } +} + +} // namespace tflm_signal +} // namespace tflite diff --git a/signal/src/circular_buffer.h b/signal/src/circular_buffer.h new file mode 100644 index 00000000..d175a9b9 --- /dev/null +++ b/signal/src/circular_buffer.h @@ -0,0 +1,118 @@ +/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef SIGNAL_SRC_CIRCULAR_BUFFER_H_ +#define SIGNAL_SRC_CIRCULAR_BUFFER_H_ + +#include +#include + +namespace tflite { +namespace tflm_signal { +// TODO(b/286250473): remove namespace once de-duped libraries above +struct CircularBuffer { + // Max number of elements, value passed-in to CircularBufferAlloc. + size_t capacity; + // Next position to read. + size_t read; + // Next position to write. + size_t write; + // Flag to indicate emptiness. + int32_t empty; + // Auto-generated size variable + int32_t buffer_size; + // Array of the circular buffer elements (integers). + int16_t* buffer; +}; + +// Returns the size of the memory that the circular buffer needs +// in order to hold `capacity` items. +size_t CircularBufferGetNeededMemory(size_t capacity); + +// Initialize an instance of the circular buffer that holds `capacity` items. +// `state` points to a memory allocation of size `state_size`. The size +// should be greater or equal to the value returned by +// CircularBufferGetNeededMemory(capacity). Fails if it isn't. +// On success, returns a pointer to the circular buffer's object. +CircularBuffer* CircularBufferInit(size_t capacity, void* state, + size_t state_size); + +// Reset a circular buffer to its initial empty state +void CircularBufferReset(CircularBuffer* cb); + +size_t CircularBufferCapacity(const CircularBuffer* cb); + +bool CircularBufferFull(const CircularBuffer* cb); + +bool CircularBufferEmpty(const CircularBuffer* cb); + +// Returns the number of elements ready to read +size_t CircularBufferAvailable(const CircularBuffer* cb); + +// Returns the number of elements available to write. +size_t CircularBufferCanWrite(const CircularBuffer* cb); + +// Adds a single `value` to the buffer and advances the write pointer. +void CircularBufferAdd(CircularBuffer* cb, int16_t value); + +// Writes `n` `values` into the buffer and advances the write pointer. +void CircularBufferWrite(CircularBuffer* cb, const int16_t* values, size_t n); + +// Writes `n` zeros into the buffer and advances the write pointer. +void CircularBufferWriteZeros(CircularBuffer* cb, size_t n); + +// Returns a pointer to a buffer where elements can be written, and +// advances the write pointer as though they have already been written. +// Fails if `n` elements are not available contiguously at the current +// write position. +int16_t* CircularBufferReserveForWrite(CircularBuffer* cb, size_t n); + +// Copies the final region (`count` elements) of the buffer `n` times, to +// the end of the buffer. +void CircularBufferExtend(CircularBuffer* cb, size_t count, int32_t n); + +// Reads a single value from the buffer and advances the read pointer +int16_t CircularBufferRemove(CircularBuffer* cb); + +// Reads the value at the given `index`, does not modify the read pointer. +int16_t CircularBufferPeek(const CircularBuffer* cb, size_t index); + +// Rewinds to restore the previous `n` values read +void CircularBufferRewind(CircularBuffer* cb, size_t n); + +// Returns a pointer directly into the circular buffer at the given `index`. +// Caller is responsible for not reading past the end. +const int16_t* CircularBufferPeekDirect(const CircularBuffer* cb, size_t index); + +// Returns a pointer into the circular buffer at the current read pointer, +// setting `n` to the number of values available to be read from here. +const int16_t* CircularBufferPeekMax(const CircularBuffer* cb, size_t* n); + +// Copies `n` `values` from the buffer and does not advance the read +// pointer and does not update the empty flag. +void CircularBufferGet(CircularBuffer* cb, size_t n, int16_t* values); + +// Discards the next `n` values by advancing the read index. +// Valid for n > 0. +void CircularBufferDiscard(CircularBuffer* cb, size_t n); + +// Shifts the buffer with `n` values (`n` can be negative) by moving +// the read index. +void CircularBufferShift(CircularBuffer* cb, int n); + +} // namespace tflm_signal +} // namespace tflite + +#endif // SIGNAL_SRC_CIRCULAR_BUFFER_H_ diff --git a/tensorflow/lite/micro/kernels/Makefile.inc b/tensorflow/lite/micro/kernels/Makefile.inc index 926ea8ae..f07af9b8 100644 --- a/tensorflow/lite/micro/kernels/Makefile.inc +++ b/tensorflow/lite/micro/kernels/Makefile.inc @@ -54,6 +54,11 @@ $(eval $(call microlite_test,kernel_signal_fft_test,\ $(TENSORFLOW_ROOT)signal/testdata/fft_test_data.cc, \ $(TENSORFLOW_ROOT)signal/micro/kernels/fft_flexbuffers_generated_data.h)) +$(eval $(call microlite_test,kernel_signal_framer_test,\ + $(TENSORFLOW_ROOT)signal/micro/kernels/framer_test.cc \ + $(TENSORFLOW_ROOT)signal/micro/kernels/framer_flexbuffers_generated_data.cc, \ + $(TENSORFLOW_ROOT)signal/micro/kernels/framer_flexbuffers_generated_data.h)) + $(eval $(call microlite_test,kernel_signal_window_test,\ $(TENSORFLOW_ROOT)signal/micro/kernels/window_test.cc \ $(TENSORFLOW_ROOT)signal/micro/kernels/window_flexbuffers_generated_data.cc, \ diff --git a/tensorflow/lite/micro/kernels/micro_ops.h b/tensorflow/lite/micro/kernels/micro_ops.h index 86a71fb0..2dd54741 100644 --- a/tensorflow/lite/micro/kernels/micro_ops.h +++ b/tensorflow/lite/micro/kernels/micro_ops.h @@ -133,6 +133,7 @@ TFLMRegistration Register_ZEROS_LIKE(); // TODO(b/160234179): Change custom OPs to also return by value. namespace tflm_signal { +TFLMRegistration* Register_FRAMER(); TFLMRegistration* Register_WINDOW(); } // namespace tflm_signal diff --git a/tensorflow/lite/micro/micro_mutable_op_resolver.h b/tensorflow/lite/micro/micro_mutable_op_resolver.h index b9e439bc..690aaa66 100644 --- a/tensorflow/lite/micro/micro_mutable_op_resolver.h +++ b/tensorflow/lite/micro/micro_mutable_op_resolver.h @@ -262,6 +262,11 @@ class MicroMutableOpResolver : public MicroOpResolver { ParseFloorMod); } + TfLiteStatus AddFramer() { + // TODO(b/286250473): change back name to "Framer" and remove namespace + return AddCustom("SignalFramer", tflite::tflm_signal::Register_FRAMER()); + } + TfLiteStatus AddFullyConnected( const TFLMRegistration& registration = Register_FULLY_CONNECTED()) { return AddBuiltin(BuiltinOperator_FULLY_CONNECTED, registration, diff --git a/tensorflow/lite/micro/tools/make/Makefile b/tensorflow/lite/micro/tools/make/Makefile index e9b69dcd..9a0ed190 100644 --- a/tensorflow/lite/micro/tools/make/Makefile +++ b/tensorflow/lite/micro/tools/make/Makefile @@ -312,8 +312,10 @@ $(TENSORFLOW_ROOT)tensorflow/lite/micro/memory_planner/linear_memory_planner_tes $(TENSORFLOW_ROOT)tensorflow/lite/micro/memory_planner/non_persistent_buffer_planner_shim_test.cc MICROLITE_CC_KERNEL_SRCS := \ +$(TENSORFLOW_ROOT)signal/micro/kernels/framer.cc \ $(TENSORFLOW_ROOT)signal/micro/kernels/rfft.cc \ $(TENSORFLOW_ROOT)signal/micro/kernels/window.cc \ +$(TENSORFLOW_ROOT)signal/src/circular_buffer.cc \ $(TENSORFLOW_ROOT)signal/src/rfft_float.cc \ $(TENSORFLOW_ROOT)signal/src/rfft_int16.cc \ $(TENSORFLOW_ROOT)signal/src/rfft_int32.cc \