import content from sso://googleplex-android/platform/vendor/google/libraries/neuralnetworks/hvxservice master
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..9b3f9d9
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# 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.
+#
+
+BasedOnStyle: Google
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+AllowShortFunctionsOnASingleLine: Inline
+ColumnLimit: 100
+TabWidth: 4
+UseTab: Never
+IndentWidth: 4
diff --git a/1.0/Android.bp b/1.0/Android.bp
new file mode 100644
index 0000000..a57f169
--- /dev/null
+++ b/1.0/Android.bp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+cc_binary {
+    name: "android.hardware.neuralnetworks@1.0-service-hvx",
+    owner: "google",
+    defaults: ["hidl_defaults"],
+    relative_install_path: "hw",
+    proprietary: true,
+    init_rc: ["android.hardware.neuralnetworks@1.0-service-hvx.rc"],
+    srcs: [
+        "Device.cpp",
+        "HexagonController.cpp",
+        "HexagonModel.cpp",
+        "HexagonOperationsCheck.cpp",
+        "HexagonOperationsPrepare.cpp",
+        "HexagonUtils.cpp",
+        "PreparedModel.cpp",
+        "Service.cpp",
+    ],
+    header_libs: [
+        "libneuralnetworks_headers",
+    ],
+    shared_libs: [
+        "libbase",
+        "libdl",
+        "libhardware",
+        "libhidlbase",
+        "libhidlmemory",
+        "libhidltransport",
+        "liblog",
+        "libutils",
+        "android.hardware.neuralnetworks@1.0",
+        "android.hardware.neuralnetworks@1.1",
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+    ],
+    static_libs: [
+        "libneuralnetworks_common",
+    ],
+}
diff --git a/1.0/Device.cpp b/1.0/Device.cpp
new file mode 100644
index 0000000..264223b
--- /dev/null
+++ b/1.0/Device.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "Device.h"
+#include <android-base/logging.h>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include "HexagonModel.h"
+#include "HexagonUtils.h"
+#include "PreparedModel.h"
+#include "ValidateHal.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+Device::Device() : mCurrentStatus(DeviceStatus::AVAILABLE) {}
+
+Device::~Device() {}
+
+static std::once_flag configure_nnlib;
+static void configureHexagon() {
+    std::call_once(configure_nnlib, []() {
+        hexagon::Controller::getInstance().config();
+        hexagon::Controller::getInstance().boost(100);
+    });
+}
+
+Return<void> Device::getCapabilities(getCapabilities_cb _hidl_cb) {
+    configureHexagon();
+
+    // These numbers are approximations for this release.
+    // TODO Change with the actual number.
+    PerformanceInfo float32Performance = {
+        .execTime = 30.0f, .powerUsage = 2.0f,
+    };
+
+    PerformanceInfo quantized8Performance = {
+        .execTime = 0.7f, .powerUsage = 0.7f,
+    };
+
+    Capabilities capabilities = {
+        .float32Performance = float32Performance, .quantized8Performance = quantized8Performance,
+    };
+
+    ErrorStatus status =
+        hexagon::isHexagonAvailable() ? ErrorStatus::NONE : ErrorStatus::DEVICE_UNAVAILABLE;
+
+    _hidl_cb(status, capabilities);
+    return Void();
+}
+
+Return<void> Device::getSupportedOperations(const Model& model,
+                                            getSupportedOperations_cb _hidl_cb) {
+    configureHexagon();
+
+    if (!nn::validateModel(model)) {
+        _hidl_cb(ErrorStatus::INVALID_ARGUMENT, std::vector<bool>{});
+        return Void();
+    }
+    if (!hexagon::isHexagonAvailable()) {
+        _hidl_cb(ErrorStatus::DEVICE_UNAVAILABLE, std::vector<bool>{});
+        return Void();
+    }
+
+    hexagon::Model hexagonModel(model);
+    std::vector<bool> supported = hexagonModel.supportedOperations();
+
+    _hidl_cb(ErrorStatus::NONE, supported);
+    return Void();
+}
+
+static void asyncPrepare(const Model& model, const sp<IPreparedModelCallback>& callback) {
+    std::shared_ptr<hexagon::Model> hexagonModel = std::make_shared<hexagon::Model>(model);
+
+    Return<void> ret;
+    if (hexagonModel->prepare()) {
+        ret = callback->notify(ErrorStatus::NONE, new PreparedModel(model, hexagonModel));
+    } else {
+        ret = callback->notify(ErrorStatus::GENERAL_FAILURE, nullptr);
+    }
+    if (!ret.isOk()) {
+        LOG(ERROR) << "Error in callback's return type: " << ret.description();
+    }
+}
+
+Return<ErrorStatus> Device::prepareModel(const Model& model,
+                                         const sp<IPreparedModelCallback>& callback) {
+    configureHexagon();
+
+    if (callback.get() == nullptr) {
+        LOG(ERROR) << "invalid callback passed to prepareModel";
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+    if (!nn::validateModel(model)) {
+        callback->notify(ErrorStatus::INVALID_ARGUMENT, nullptr);
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+    if (!hexagon::isHexagonAvailable()) {
+        callback->notify(ErrorStatus::DEVICE_UNAVAILABLE, nullptr);
+        return ErrorStatus::DEVICE_UNAVAILABLE;
+    }
+
+    // TODO: once nnlib hanging issue is resolved, make this function
+    // asynchronous again
+    asyncPrepare(model, callback);
+
+    return ErrorStatus::NONE;
+}
+
+Return<DeviceStatus> Device::getStatus() {
+    configureHexagon();
+    mCurrentStatus =
+        hexagon::isHexagonAvailable() ? DeviceStatus::AVAILABLE : DeviceStatus::OFFLINE;
+    return mCurrentStatus;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/Device.h b/1.0/Device.h
new file mode 100644
index 0000000..cae055c
--- /dev/null
+++ b/1.0/Device.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_NEURALNETWORKS_V1_0_DEVICE_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_0_DEVICE_H
+
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include <string>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+struct Device : public IDevice {
+    Device();
+    ~Device() override;
+
+    // Methods from IDevice follow.
+    Return<void> getCapabilities(getCapabilities_cb _hidl_cb) override;
+    Return<void> getSupportedOperations(const Model& model,
+                                        getSupportedOperations_cb _hidl_cb) override;
+    Return<ErrorStatus> prepareModel(const Model& model,
+                                     const sp<IPreparedModelCallback>& callback) override;
+    Return<DeviceStatus> getStatus() override;
+
+   private:
+    DeviceStatus mCurrentStatus;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_0_DEVICE_H
diff --git a/1.0/HexagonController.cpp b/1.0/HexagonController.cpp
new file mode 100644
index 0000000..d2753ac
--- /dev/null
+++ b/1.0/HexagonController.cpp
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "HexagonController.h"
+
+#define LOAD_HEXAGON_FUNCTION(name) \
+    mFn_##name = loadFunction<hexagon_nn_controller_##name##_fn>("hexagon_nn_controller_" #name);
+
+#define CLOSE_HEXAGON_FUNCTION(name) mFn_##name = nullptr;
+
+#define FOR_EACH_FUNCTION(MACRO)   \
+    MACRO(init)                    \
+    MACRO(getlog)                  \
+    MACRO(snpprint)                \
+    MACRO(set_debug_level)         \
+    MACRO(prepare)                 \
+    MACRO(append_node)             \
+    MACRO(append_const_node)       \
+    MACRO(execute_new)             \
+    MACRO(execute)                 \
+    MACRO(teardown)                \
+    MACRO(get_perfinfo)            \
+    MACRO(reset_perfinfo)          \
+    MACRO(version)                 \
+    MACRO(last_execution_cycles)   \
+    MACRO(GetHexagonBinaryVersion) \
+    MACRO(PrintLog)                \
+    MACRO(op_name_to_id)           \
+    MACRO(op_id_to_name)           \
+    MACRO(disable_dcvs)            \
+    MACRO(set_powersave_level)     \
+    MACRO(config)                  \
+    MACRO(get_dsp_offset)          \
+    MACRO(boost)                   \
+    MACRO(slow)
+
+#define CONTROLLER_CHECK(function, ...)    \
+    if (mFn_##function == nullptr) {       \
+        return -1;                         \
+    }                                      \
+    int err = mFn_##function(__VA_ARGS__); \
+    if (err != 0) {                        \
+        return err;                        \
+    }                                      \
+    return 0;
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+const char Controller::kFilename[] = "libhexagon_nn_controller.so";
+
+Controller::Controller() {
+    openNnlib();
+}
+
+Controller::~Controller() {
+    closeNnlib();
+}
+
+bool Controller::openNnlib() {
+    mHandle = dlopen(kFilename, RTLD_LAZY | RTLD_LOCAL);
+    HEXAGON_SOFT_ASSERT_NE(mHandle, 0,
+                           "FAILED TO LOAD LIBRARY " /* << kFilename << ": " << dlerror()*/);
+    FOR_EACH_FUNCTION(LOAD_HEXAGON_FUNCTION)
+    return true;
+}
+
+bool Controller::closeNnlib() {
+    FOR_EACH_FUNCTION(CLOSE_HEXAGON_FUNCTION)
+    if (mHandle != nullptr) {
+        int err = dlclose(mHandle);
+        mHandle = nullptr;
+        HEXAGON_SOFT_ASSERT_EQ(err, 0, "FAILED TO CLOSE LIBRARY " << kFilename);
+    }
+    return true;
+}
+
+bool Controller::resetNnlib() {
+    return closeNnlib() && openNnlib();
+}
+
+Controller& Controller::getInstance() {
+    static Controller instance{};
+    return instance;
+}
+
+int Controller::init(hexagon_nn_nn_id* g) {
+    CONTROLLER_CHECK(init, g);
+}
+
+int Controller::getlog(hexagon_nn_nn_id id, unsigned char* buf, uint32_t length) {
+    CONTROLLER_CHECK(getlog, id, buf, length);
+}
+
+int Controller::snpprint(hexagon_nn_nn_id id, unsigned char* buf, uint32_t length) {
+    CONTROLLER_CHECK(snpprint, id, buf, length);
+}
+
+int Controller::set_debug_level(hexagon_nn_nn_id id, int level) {
+    CONTROLLER_CHECK(set_debug_level, id, level);
+}
+
+int Controller::prepare(hexagon_nn_nn_id id) {
+    CONTROLLER_CHECK(prepare, id);
+}
+
+int Controller::append_node(hexagon_nn_nn_id id, uint32_t node_id, op_type operation,
+                            hexagon_nn_padding_type padding, const hexagon_nn_input* inputs,
+                            uint32_t num_inputs, const hexagon_nn_output* outputs,
+                            uint32_t num_outputs) {
+    CONTROLLER_CHECK(append_node, id, node_id, operation, padding, inputs, num_inputs, outputs,
+                     num_outputs);
+}
+
+int Controller::append_const_node(hexagon_nn_nn_id id, uint32_t node_id, uint32_t batches,
+                                  uint32_t height, uint32_t width, uint32_t depth,
+                                  const uint8_t* data, uint32_t data_len) {
+    CONTROLLER_CHECK(append_const_node, id, node_id, batches, height, width, depth, data, data_len);
+}
+
+int Controller::execute_new(hexagon_nn_nn_id id, const hexagon_nn_tensordef* inputs,
+                            uint32_t n_inputs, hexagon_nn_tensordef* outputs, uint32_t n_outputs) {
+    CONTROLLER_CHECK(execute_new, id, inputs, n_inputs, outputs, n_outputs);
+}
+
+int Controller::execute(hexagon_nn_nn_id id, uint32_t batches_in, uint32_t height_in,
+                        uint32_t width_in, uint32_t depth_in, const uint8_t* data_in,
+                        uint32_t data_len_in, uint32_t* batches_out, uint32_t* height_out,
+                        uint32_t* width_out, uint32_t* depth_out, uint8_t* data_out,
+                        uint32_t data_out_max, uint32_t* data_out_size) {
+    CONTROLLER_CHECK(execute, id, batches_in, height_in, width_in, depth_in, data_in, data_len_in,
+                     batches_out, height_out, width_out, depth_out, data_out, data_out_max,
+                     data_out_size);
+}
+
+int Controller::teardown(hexagon_nn_nn_id id) {
+    CONTROLLER_CHECK(teardown, id);
+}
+
+int Controller::get_perfinfo(hexagon_nn_nn_id id, hexagon_nn_perfinfo* info_out,
+                             unsigned int info_out_len, unsigned int* n_items_out) {
+    CONTROLLER_CHECK(get_perfinfo, id, info_out, info_out_len, n_items_out);
+}
+
+int Controller::reset_perfinfo(hexagon_nn_nn_id id, uint32_t event) {
+    CONTROLLER_CHECK(reset_perfinfo, id, event);
+}
+
+int Controller::version(int* ver) {
+    CONTROLLER_CHECK(version, ver);
+}
+
+int Controller::last_execution_cycles(hexagon_nn_nn_id id, unsigned int* cycles_lo,
+                                      unsigned int* cycles_hi) {
+    CONTROLLER_CHECK(last_execution_cycles, id, cycles_lo, cycles_hi);
+}
+
+int Controller::GetHexagonBinaryVersion(int* ver) {
+    CONTROLLER_CHECK(GetHexagonBinaryVersion, ver);
+}
+
+int Controller::PrintLog(const uint8_t* data_in, unsigned int data_in_len) {
+    CONTROLLER_CHECK(PrintLog, data_in, data_in_len);
+}
+
+int Controller::op_name_to_id(const char* name, unsigned int* id) {
+    CONTROLLER_CHECK(op_name_to_id, name, id);
+}
+
+int Controller::op_id_to_name(const unsigned int id, char* name, int name_len) {
+    CONTROLLER_CHECK(op_id_to_name, id, name, name_len);
+}
+
+int Controller::disable_dcvs() {
+    CONTROLLER_CHECK(disable_dcvs);
+}
+
+int Controller::set_powersave_level(unsigned int level) {
+    CONTROLLER_CHECK(set_powersave_level, level);
+}
+
+int Controller::config() {
+    CONTROLLER_CHECK(config);
+}
+
+unsigned int Controller::get_dsp_offset() {
+    CONTROLLER_CHECK(get_dsp_offset);
+}
+
+int Controller::boost(int bus_usage) {
+    CONTROLLER_CHECK(boost, bus_usage);
+}
+
+int Controller::slow() {
+    CONTROLLER_CHECK(slow);
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/HexagonController.h b/1.0/HexagonController.h
new file mode 100644
index 0000000..ce7e8e7
--- /dev/null
+++ b/1.0/HexagonController.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_V1_0_HEXAGON_CONTROLLER_H
+#define ANDROID_HARDWARE_V1_0_HEXAGON_CONTROLLER_H
+
+#include <android-base/logging.h>
+#include "HexagonUtils.h"
+#include "dlfcn.h"
+#include "hexagon_nn_controller/hexagon_nn_controller.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+// interface wrapper
+class Controller {
+    // methods
+   private:
+    Controller();
+    ~Controller();
+    Controller(const Controller&) = delete;
+    Controller(Controller&&) = delete;
+    Controller& operator=(const Controller&) = delete;
+    Controller& operator=(Controller&&) = delete;
+
+    template <typename Function>
+    Function loadFunction(const char* name) {
+        void* fn = dlsym(mHandle, name);
+        if (fn == nullptr) {
+            LOG(ERROR) << "loadFunction -- failed to load function " << name;
+        }
+        return reinterpret_cast<Function>(fn);
+    }
+
+    bool openNnlib();
+    bool closeNnlib();
+
+   public:
+    static Controller& getInstance();
+    bool resetNnlib();
+
+    int init(hexagon_nn_nn_id* g);
+
+    int getlog(hexagon_nn_nn_id id, unsigned char* buf, uint32_t length);
+
+    int snpprint(hexagon_nn_nn_id id, unsigned char* buf, uint32_t length);
+
+    int set_debug_level(hexagon_nn_nn_id id, int level);
+
+    int prepare(hexagon_nn_nn_id id);
+
+    int append_node(hexagon_nn_nn_id id, uint32_t node_id, op_type operation,
+                    hexagon_nn_padding_type padding, const hexagon_nn_input* inputs,
+                    uint32_t num_inputs, const hexagon_nn_output* outputs, uint32_t num_outputs);
+
+    int append_const_node(hexagon_nn_nn_id id, uint32_t node_id, uint32_t batches, uint32_t height,
+                          uint32_t width, uint32_t depth, const uint8_t* data, uint32_t data_len);
+
+    int execute_new(hexagon_nn_nn_id id, const hexagon_nn_tensordef* inputs, uint32_t n_inputs,
+                    hexagon_nn_tensordef* outputs, uint32_t n_outputs);
+
+    int execute(hexagon_nn_nn_id id, uint32_t batches_in, uint32_t height_in, uint32_t width_in,
+                uint32_t depth_in, const uint8_t* data_in, uint32_t data_len_in,
+                uint32_t* batches_out, uint32_t* height_out, uint32_t* width_out,
+                uint32_t* depth_out, uint8_t* data_out, uint32_t data_out_max,
+                uint32_t* data_out_size);
+
+    int teardown(hexagon_nn_nn_id id);
+
+    int get_perfinfo(hexagon_nn_nn_id id, hexagon_nn_perfinfo* info_out, unsigned int info_out_len,
+                     unsigned int* n_items_out);
+
+    int reset_perfinfo(hexagon_nn_nn_id id, uint32_t event);
+
+    int version(int* ver);
+
+    int last_execution_cycles(hexagon_nn_nn_id id, unsigned int* cycles_lo,
+                              unsigned int* cycles_hi);
+
+    int GetHexagonBinaryVersion(int* ver);
+
+    int PrintLog(const uint8_t* data_in, unsigned int data_in_len);
+
+    int op_name_to_id(const char* name, unsigned int* id);
+
+    int op_id_to_name(const unsigned int id, char* name, int name_len);
+
+    int disable_dcvs();
+
+    int set_powersave_level(unsigned int level);
+
+    int config();
+
+    unsigned int get_dsp_offset();
+
+    int boost(int bus_usage);
+
+    int slow();
+
+    // members
+   private:
+    static const char kFilename[];
+    void* mHandle;
+    hexagon_nn_controller_init_fn mFn_init;
+    hexagon_nn_controller_getlog_fn mFn_getlog;
+    hexagon_nn_controller_snpprint_fn mFn_snpprint;
+    hexagon_nn_controller_set_debug_level_fn mFn_set_debug_level;
+    hexagon_nn_controller_prepare_fn mFn_prepare;
+    hexagon_nn_controller_append_node_fn mFn_append_node;
+    hexagon_nn_controller_append_const_node_fn mFn_append_const_node;
+    hexagon_nn_controller_execute_new_fn mFn_execute_new;
+    hexagon_nn_controller_execute_fn mFn_execute;
+    hexagon_nn_controller_teardown_fn mFn_teardown;
+    hexagon_nn_controller_get_perfinfo_fn mFn_get_perfinfo;
+    hexagon_nn_controller_reset_perfinfo_fn mFn_reset_perfinfo;
+    hexagon_nn_controller_version_fn mFn_version;
+    hexagon_nn_controller_last_execution_cycles_fn mFn_last_execution_cycles;
+    hexagon_nn_controller_GetHexagonBinaryVersion_fn mFn_GetHexagonBinaryVersion;
+    hexagon_nn_controller_PrintLog_fn mFn_PrintLog;
+    hexagon_nn_controller_op_name_to_id_fn mFn_op_name_to_id;
+    hexagon_nn_controller_op_id_to_name_fn mFn_op_id_to_name;
+    hexagon_nn_controller_disable_dcvs_fn mFn_disable_dcvs;
+    hexagon_nn_controller_set_powersave_level_fn mFn_set_powersave_level;
+    hexagon_nn_controller_config_fn mFn_config;
+    hexagon_nn_controller_get_dsp_offset_fn mFn_get_dsp_offset;
+    hexagon_nn_controller_boost_fn mFn_boost;
+    hexagon_nn_controller_slow_fn mFn_slow;
+};
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_V1_0_HEXAGON_CONTROLLER_H
diff --git a/1.0/HexagonModel.cpp b/1.0/HexagonModel.cpp
new file mode 100644
index 0000000..859d5fd
--- /dev/null
+++ b/1.0/HexagonModel.cpp
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "HexagonModel.h"
+#include <numeric>
+#include <unordered_set>
+#include "HexagonOperations.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+static std::vector<OperandInfo> getOperandsInfo(const NeuralnetworksModel& model,
+                                                const std::vector<RunTimePoolInfo>& pools) {
+    std::vector<OperandInfo> info(model.operands.size());
+    for (size_t i = 0; i < model.operands.size(); ++i) {
+        const Operand& operand = model.operands[i];
+        info[i] = {
+            .type = operand.type,
+            .dimensions = operand.dimensions,
+            .scale = operand.scale,
+            .zeroPoint = operand.zeroPoint,
+            .lifetime = operand.lifetime,
+            .buffer = const_cast<uint8_t*>(getData(operand, model.operandValues, pools)),
+            .length = operand.location.length,
+        };
+    }
+    return info;
+}
+
+Model::Model(const NeuralnetworksModel& model) : mGraphId(0), mNodeCount(0), mCompiled(false) {
+    mPools = mapPools(model.pools);
+    mOperands = getOperandsInfo(model, mPools);
+    std::for_each(mPools.begin(), mPools.end(), [](RunTimePoolInfo& mem) { mem.update(); });
+
+    mOperations = model.operations;
+    mInputs = model.inputIndexes;
+    mOutputs = model.outputIndexes;
+}
+
+Model::Model(Model&& other) {
+    *this = std::move(other);
+}
+
+Model& Model::operator=(Model&& other) {
+    if (this != &other) {
+        mNodeCount = other.mNodeCount;
+        mGraphId = other.mGraphId;
+        mCompiled = other.mCompiled;
+        mOperands = std::move(other.mOperands);
+        mOperations = std::move(other.mOperations);
+        mInputs = std::move(other.mInputs);
+        mOutputs = std::move(other.mOutputs);
+        mPools = std::move(other.mPools);
+        other.mNodeCount = 0;
+        other.mGraphId = {};
+        other.mCompiled = false;
+    }
+    return *this;
+}
+
+Model::~Model() {
+    clearModel();
+}
+
+std::string Model::getLog() {
+    char buffer[16 * 1024];
+    int err = hexagon::Controller::getInstance().getlog(
+        mGraphId, reinterpret_cast<uint8_t*>(buffer), sizeof(buffer));
+    HEXAGON_SOFT_ASSERT_EQ(0, err, "failed getLog");
+    return buffer;
+}
+
+std::string Model::getGraph() {
+    char buffer[16 * 1024];
+    int err = hexagon::Controller::getInstance().snpprint(
+        mGraphId, reinterpret_cast<uint8_t*>(buffer), sizeof(buffer));
+    HEXAGON_SOFT_ASSERT_EQ(0, err, "failed getGraph");
+    return buffer;
+}
+
+uint32_t Model::getNextNode() {
+    return ++mNodeCount;
+}
+
+const int32_t* Model::getPointer(uint32_t operand) {
+    return reinterpret_cast<const int32_t*>(mOperands[operand].buffer);
+}
+
+Shape Model::getShape(uint32_t operand) {
+    return {
+        .type = mOperands[operand].type,
+        .dimensions = mOperands[operand].dimensions,
+        .scale = mOperands[operand].scale,
+        .offset = mOperands[operand].zeroPoint,
+    };
+}
+
+bool Model::setShape(uint32_t operand, const Shape& shape) {
+    const hexagon_nn_output& output = mOperands[operand].hexagon_output;
+    HEXAGON_SOFT_ASSERT_EQ(output, hexagon_nn_output{}, "Output has already been set");
+    // mOperands[operand].type       = shape.type;
+    mOperands[operand].dimensions = shape.dimensions;
+    // mOperands[operand].scale      = shape.scale;
+    // mOperands[operand].zeroPoint  = shape.offset;
+    return true;
+}
+
+bool Model::isConstant(uint32_t operand) {
+    OperandLifeTime lifetime = mOperands[operand].lifetime;
+    return lifetime == OperandLifeTime::CONSTANT_COPY ||
+           lifetime == OperandLifeTime::CONSTANT_REFERENCE;
+}
+
+hexagon_nn_input Model::createTensorInternal(uint32_t B, uint32_t H, uint32_t W, uint32_t D,
+                                             const uint8_t* ptr, size_t size) {
+    uint32_t node = getNextNode();
+    bool success = hexagon::Controller::getInstance().append_const_node(mGraphId, node, B, H, W, D,
+                                                                        ptr, size) == 0;
+    HEXAGON_SOFT_ASSERT(success, "Failed to create tensor");
+    return {.src_id = node, .output_idx = 0};
+}
+
+hexagon_nn_input Model::createShape(uint32_t B, uint32_t H, uint32_t W, uint32_t D) {
+    uint32_t dump = 0;
+    return createTensorInternal(B, H, W, D, reinterpret_cast<uint8_t*>(&dump), sizeof(dump));
+}
+
+hexagon_nn_input Model::addOperand(uint32_t operandIndex) {
+    const OperandInfo& operand = mOperands[operandIndex];
+    std::vector<uint32_t> dims = getAlignedDimensions(operand.dimensions, 4);
+    HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Rank must be at most 4");
+    hexagon_nn_input result =
+        createTensorInternal(dims[0], dims[1], dims[2], dims[3], operand.buffer, operand.length);
+    HEXAGON_SOFT_ASSERT_NE(hexagon_nn_input{}, result, "Failed to add operand");
+    return result;
+}
+
+const hexagon_nn_input& Model::getTensor(uint32_t operand) {
+    hexagon_nn_input& tensor = mOperands[operand].hexagon_input;
+    if (tensor == hexagon_nn_input{}) {
+        tensor = addOperand(operand);
+    }
+    return tensor;
+}
+
+const hexagon_nn_input& Model::getQuantizationMin(uint32_t operand) {
+    OperandInfo& operandInfo = mOperands[operand];
+    if (operandInfo.hexagon_input_min == hexagon_nn_input{}) {
+        float real_value =
+            operandInfo.type == OperandType::TENSOR_QUANT8_ASYMM
+                ? (std::numeric_limits<uint8_t>::min() - operandInfo.zeroPoint) * operandInfo.scale
+                : std::numeric_limits<uint32_t>::min() * operandInfo.scale;
+        operandInfo.hexagon_input_min = createValues<float>({real_value});
+    }
+    return operandInfo.hexagon_input_min;
+}
+
+const hexagon_nn_input& Model::getQuantizationMax(uint32_t operand) {
+    OperandInfo& operandInfo = mOperands[operand];
+    if (operandInfo.hexagon_input_max == hexagon_nn_input{}) {
+        float real_value =
+            operandInfo.type == OperandType::TENSOR_QUANT8_ASYMM
+                ? (std::numeric_limits<uint8_t>::max() - operandInfo.zeroPoint) * operandInfo.scale
+                : std::numeric_limits<uint32_t>::max() * operandInfo.scale;
+        operandInfo.hexagon_input_max = createValues<float>({real_value});
+    }
+    return operandInfo.hexagon_input_max;
+}
+
+hexagon_nn_padding_type Model::getPadding(uint32_t operand) {
+    const int32_t padding = getScalar<int32_t>(operand);
+    return hexagon::getPadding(padding);
+}
+
+hexagon_nn_input Model::createQuantizationValue(uint32_t operand, int32_t quant_value) {
+    OperandInfo& operandInfo = mOperands[operand];
+    float real_value = (quant_value - operandInfo.zeroPoint) * operandInfo.scale;
+    return createValues<float>({real_value});
+}
+
+hexagon_nn_input Model::createConvFilterTensor(uint32_t operand) {
+    OperandInfo& operandInfo = mOperands[operand];
+    std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
+    HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
+    // NHWC --> HWCN
+    if (getShape(operand).type == OperandType::TENSOR_FLOAT32) {
+        std::vector<float> transposed =
+            transpose<float>(dims[0], dims[1] * dims[2] * dims[3],
+                             reinterpret_cast<const float*>(operandInfo.buffer));
+        return createTensorInternal(dims[1], dims[2], dims[3], dims[0],
+                                    reinterpret_cast<const uint8_t*>(transposed.data()),
+                                    operandInfo.length);
+    } else {
+        std::vector<uint8_t> transposed =
+            transpose<uint8_t>(dims[0], dims[1] * dims[2] * dims[3],
+                               reinterpret_cast<const uint8_t*>(operandInfo.buffer));
+        return createTensorInternal(dims[1], dims[2], dims[3], dims[0],
+                                    reinterpret_cast<const uint8_t*>(transposed.data()),
+                                    operandInfo.length);
+    }
+}
+
+hexagon_nn_input Model::createDepthwiseFilterTensor(uint32_t operand, int32_t depth_multiplier) {
+    OperandInfo& operandInfo = mOperands[operand];
+    std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
+    HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
+    // NHWC --> HWCN
+    return createTensorInternal(dims[1], dims[2], dims[3] / depth_multiplier,
+                                dims[0] * depth_multiplier, operandInfo.buffer, operandInfo.length);
+}
+
+hexagon_nn_input Model::createFullyConnectedWeightTensor(uint32_t operand) {
+    OperandInfo& operandInfo = mOperands[operand];
+    std::vector<uint32_t> dims = getAlignedDimensions(mOperands[operand].dimensions, 4);
+    HEXAGON_SOFT_ASSERT_NE(0ul, dims.size(), "Need at most 4 dimensions");
+    // WC --> CW
+    uint32_t num_units = dims[0] * dims[1] * dims[2];
+    uint32_t input_size = dims[3];
+    if (getShape(operand).type == OperandType::TENSOR_FLOAT32) {
+        std::vector<float> transposed = transpose<float>(
+            num_units, input_size, reinterpret_cast<const float*>(operandInfo.buffer));
+        return createTensorInternal(1, 1, input_size, num_units,
+                                    reinterpret_cast<const uint8_t*>(transposed.data()),
+                                    operandInfo.length);
+    } else {
+        std::vector<uint8_t> transposed = transpose<uint8_t>(
+            num_units, input_size, reinterpret_cast<const uint8_t*>(operandInfo.buffer));
+        return createTensorInternal(1, 1, input_size, num_units,
+                                    reinterpret_cast<const uint8_t*>(transposed.data()),
+                                    operandInfo.length);
+    }
+}
+
+op_type Model::getFloatActivation(uint32_t operand) {
+    return getFloatActivationFunction(getScalar<FusedActivationFunc>(operand));
+}
+
+op_type Model::getQuantizedActivation(uint32_t operand) {
+    return getQuantizedActivationFunction(getScalar<FusedActivationFunc>(operand));
+}
+
+static bool verifyOperationInputs(const std::vector<hexagon_nn_input>& inputs) {
+    for (const hexagon_nn_input& input : inputs) {
+        if (input == hexagon_nn_input{}) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool verifyOperationOutputs(const std::vector<hexagon_nn_output>& outputs) {
+    for (const hexagon_nn_output& output : outputs) {
+        if (output == hexagon_nn_output{}) {
+            return false;
+        }
+    }
+    return true;
+}
+
+uint32_t Model::addOperationInternal(op_type op, hexagon_nn_padding_type pad,
+                                     const std::vector<hexagon_nn_input>& inputs,
+                                     const std::vector<hexagon_nn_output>& outputs) {
+    HEXAGON_SOFT_ASSERT(verifyOperationInputs(inputs),
+                        "error adding operation: one or more inputs is invalid");
+    HEXAGON_SOFT_ASSERT(verifyOperationOutputs(outputs),
+                        "error adding operation: one or more outputs is invalid");
+    uint32_t node = getNextNode();
+    return hexagon::Controller::getInstance().append_node(mGraphId, node, op, pad, inputs.data(),
+                                                          inputs.size(), outputs.data(),
+                                                          outputs.size()) == 0
+               ? node
+               : 0;
+}
+
+std::vector<hexagon_nn_output> Model::getHexagonOutputs(const std::vector<uint32_t>& operands) {
+    std::vector<hexagon_nn_output> outputs;
+    for (uint32_t index : operands) {
+        const OperandInfo& operand = mOperands[index];
+        outputs.push_back(make_hexagon_nn_output(operand.dimensions, getSize(operand.type)));
+        if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
+            outputs.push_back(make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)));
+            outputs.push_back(make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)));
+        }
+    }
+    return outputs;
+}
+
+bool Model::registerHexagonInputs(const std::vector<uint32_t>& operands, uint32_t node) {
+    uint32_t idx = 0;
+    for (uint32_t i = 0; i < static_cast<uint32_t>(operands.size()); ++i) {
+        OperandInfo& operand = mOperands[operands[i]];
+        HEXAGON_SOFT_ASSERT_EQ(operand.hexagon_input, hexagon_nn_input{},
+                               "Error: operation output has already been registered");
+        operand.hexagon_input = {.src_id = node, .output_idx = idx++};
+        if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
+            operand.hexagon_input_min = {.src_id = node, .output_idx = idx++};
+            operand.hexagon_input_max = {.src_id = node, .output_idx = idx++};
+        }
+    }
+    return true;
+}
+
+bool Model::addBasicOperation(op_type op, hexagon_nn_padding_type pad,
+                              const std::vector<hexagon_nn_input>& inputs,
+                              const std::vector<uint32_t>& outputs) {
+    std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
+    uint32_t node = addOperationInternal(op, pad, inputs, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
+    return registerHexagonInputs(outputs, node);
+}
+
+std::vector<hexagon_nn_input> Model::setupActivationArgs(op_type op) {
+    switch (op) {
+        case OP_Nop:
+            return {};
+        case OP_Relu_f:
+            FALLTHROUGH_INTENDED;
+        case OP_QuantizedRelu_8:
+            return {};
+        case OP_ReluX_f:
+            FALLTHROUGH_INTENDED;
+        case OP_QuantizedReluX_8:
+            return {createValues<float>({6.0f})};
+        case OP_Clamp_f:
+            FALLTHROUGH_INTENDED;
+        case OP_QuantizedClamp_8:
+            return {createValues<float>({-1.0f}), createValues<float>({1.0f})};
+        default:
+            HEXAGON_SOFT_ASSERT(false, "Unknown activation symbol " << op);
+    }
+}
+
+bool Model::addFloatOperationWithActivation(op_type op, hexagon_nn_padding_type pad,
+                                            op_type activation,
+                                            const std::vector<hexagon_nn_input>& inputs,
+                                            const std::vector<uint32_t>& outputs) {
+    std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
+    std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
+
+    uint32_t node = addOperationInternal(op, pad, inputs, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
+
+    std::vector<hexagon_nn_input> buffer_in = {{.src_id = node, .output_idx = 0}};
+    buffer_in.insert(buffer_in.end(), actArgs.begin(), actArgs.end());
+    node = addOperationInternal(activation, NN_PAD_NA, buffer_in, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
+
+    return registerHexagonInputs(outputs, node);
+}
+
+bool Model::addQuant8OperationWithActivation(op_type op, hexagon_nn_padding_type pad,
+                                             op_type activation,
+                                             const std::vector<hexagon_nn_input>& inputs,
+                                             const std::vector<uint32_t>& outputs) {
+    std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
+    std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
+
+    uint32_t node = addOperationInternal(op, pad, inputs, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
+
+    std::vector<hexagon_nn_input> buffer_in = {{.src_id = node, .output_idx = 0},
+                                               {.src_id = node, .output_idx = 1},
+                                               {.src_id = node, .output_idx = 2}};
+    buffer_in.insert(buffer_in.end(), actArgs.begin(), actArgs.end());
+    node = addOperationInternal(activation, NN_PAD_NA, buffer_in, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
+
+    return registerHexagonInputs(outputs, node);
+}
+
+bool Model::addFusedFloatOperation(op_type op, hexagon_nn_padding_type pad,
+                                   const hexagon_nn_input& bias, op_type activation,
+                                   const std::vector<hexagon_nn_input>& inputs,
+                                   const std::vector<uint32_t>& outputs) {
+    HEXAGON_SOFT_ASSERT_EQ(1, outputs.size(), "addFusedFloatOperation requires 1 output");
+    std::vector<hexagon_nn_output> outs = getHexagonOutputs(outputs);
+    std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
+    uint32_t node;
+
+    node = addOperationInternal(op, pad, inputs, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
+
+    if (bias != hexagon_nn_input{}) {
+        const hexagon_nn_input buffer1_in = {.src_id = node, .output_idx = 0};
+        node = addOperationInternal(OP_BiasAdd_f, NN_PAD_NA, {buffer1_in, bias}, outs);
+        HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding bias operation");
+    }
+
+    if (activation != OP_Nop) {
+        std::vector<hexagon_nn_input> buffer2_in = {{.src_id = node, .output_idx = 0}};
+        buffer2_in.insert(buffer2_in.end(), actArgs.begin(), actArgs.end());
+        node = addOperationInternal(activation, NN_PAD_NA, buffer2_in, outs);
+        HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
+    }
+
+    return registerHexagonInputs(outputs, node);
+}
+
+bool Model::addFusedQuant8Operation(op_type op, hexagon_nn_padding_type pad,
+                                    const std::vector<hexagon_nn_input>& bias, op_type activation,
+                                    const std::vector<hexagon_nn_input>& inputs,
+                                    const std::vector<uint32_t>& outputs) {
+    HEXAGON_SOFT_ASSERT_EQ(1, outputs.size(), "addFusedQuant8Operation requires 1 output");
+    std::vector<hexagon_nn_input> actArgs = setupActivationArgs(activation);
+    const hexagon_nn_input& new_min = getQuantizationMin(outputs[0]);
+    const hexagon_nn_input& new_max = getQuantizationMax(outputs[0]);
+    uint32_t node;
+
+    hexagon_nn_output tensor_out8 =
+        make_hexagon_nn_output(mOperands[outputs[0]].dimensions, sizeof(uint8_t));
+    hexagon_nn_output tensor_out32 =
+        make_hexagon_nn_output(mOperands[outputs[0]].dimensions, sizeof(int32_t));
+    hexagon_nn_output scalar_out32 = make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float));
+
+    std::vector<hexagon_nn_output> out8 = {tensor_out8, scalar_out32, scalar_out32};
+    std::vector<hexagon_nn_output> out32 = {tensor_out32, scalar_out32, scalar_out32};
+
+    // base operation
+    node = addOperationInternal(op, pad, inputs, out32);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding base operation");
+    hexagon_nn_input previous = {.src_id = node, .output_idx = 0};
+    hexagon_nn_input previous_min = {.src_id = node, .output_idx = 1};
+    hexagon_nn_input previous_max = {.src_id = node, .output_idx = 2};
+
+    // add bias
+    if (bias.size() == 3) {
+        node = addOperationInternal(
+            OP_QuantizedBiasAdd_32p32to32, NN_PAD_NA,
+            {previous, bias[0], previous_min, previous_max, bias[1], bias[2]}, out32);
+        HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding bias operation");
+        previous.src_id = node;
+        previous_min.src_id = node;
+        previous_max.src_id = node;
+    }
+
+    // requantize
+    node = addOperationInternal(OP_Requantize_32to8, NN_PAD_NA,
+                                {previous, previous_min, previous_max, new_min, new_max}, out8);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding requantize operation");
+    previous.src_id = node;
+    previous_min.src_id = node;
+    previous_max.src_id = node;
+
+    // activation
+    if (activation != OP_Nop) {
+        std::vector<hexagon_nn_input> buffer = {previous, previous_min, previous_max};
+        buffer.insert(buffer.end(), actArgs.begin(), actArgs.end());
+        node = addOperationInternal(activation, NN_PAD_NA, buffer, out8);
+        HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding activation operation");
+    }
+
+    return registerHexagonInputs(outputs, node);
+}
+
+bool Model::verifyOperations() {
+    std::vector<bool> supported = supportedOperations();
+    return std::all_of(supported.begin(), supported.end(), [](bool valid) { return valid; });
+}
+
+bool Model::verifyOperands() {
+    for (const OperandInfo& operand : mOperands) {
+        HEXAGON_SOFT_ASSERT_GE(4ul, operand.dimensions.size(),
+                               "Operand must have at most 4 dimensions");
+        for (uint32_t dim : operand.dimensions) {
+            HEXAGON_SOFT_ASSERT_NE(0, dim, "At least one operand with unknown dimension");
+        }
+    }
+    return true;
+}
+
+bool Model::addInputs() {
+    // prepare OP_INPUT's outputs
+    std::vector<hexagon_nn_output> outs;
+    for (size_t i = 0; i < mInputs.size(); ++i) {
+        OperandInfo& operand = mOperands[mInputs[i]];
+        outs.push_back(make_hexagon_nn_output(operand.dimensions, getSize(operand.type)));
+    }
+
+    // add single input node for entire graph
+    uint32_t node = addOperationInternal(OP_INPUT, NN_PAD_NA, {}, outs);
+    HEXAGON_SOFT_ASSERT_NE(0, node, "Error adding input operation");
+
+    // update operand information
+    for (size_t i = 0; i < mInputs.size(); ++i) {
+        OperandInfo& operand = mOperands[mInputs[i]];
+        operand.hexagon_input = {.src_id = node, .output_idx = static_cast<uint32_t>(i)};
+    }
+
+    return true;
+}
+
+bool Model::addOperations() {
+    for (const Operation& operation : mOperations) {
+        OperationType operationType = operation.type;
+
+        // For now, the operation type is always the same as its first operand
+        // parameter. If this changes in the future, this line of code will need
+        // to be updated.
+        OperandType operandType = mOperands[operation.inputs[0]].type;
+
+        OperationTuple opTuple = std::make_pair(operationType, operandType);
+        HEXAGON_SOFT_ASSERT(
+            getOperationPrepareTable().find(opTuple) != getOperationPrepareTable().end(),
+            "Operation not found");
+        bool success =
+            getOperationPrepareTable()[opTuple](operation.inputs, operation.outputs, this);
+        HEXAGON_SOFT_ASSERT(success, "error adding operation");
+    }
+    return true;
+}
+
+bool Model::addOutputs() {
+    // prepare OP_OUTPUT's inputs
+    std::vector<hexagon_nn_input> ins;
+    for (size_t out : mOutputs) {
+        OperandInfo& operand = mOperands[out];
+        HEXAGON_SOFT_ASSERT_NE(operand.hexagon_input, hexagon_nn_input{},
+                               "output operand has not been registered");
+
+        if (operand.type == OperandType::TENSOR_QUANT8_ASYMM) {
+            // Adjust quantized range of outputs
+            uint32_t dequant = addOperationInternal(
+                OP_Dequantize, NN_PAD_NA,
+                {operand.hexagon_input, operand.hexagon_input_min, operand.hexagon_input_max},
+                {make_hexagon_nn_output(operand.dimensions, sizeof(float))});
+            uint32_t quant =
+                addOperationInternal(OP_Quantize, NN_PAD_NA,
+                                     {{.src_id = dequant, .output_idx = 0},
+                                      createQuantizationValue(out, 0),
+                                      createQuantizationValue(out, 255)},
+                                     {make_hexagon_nn_output(operand.dimensions, sizeof(uint8_t)),
+                                      make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float)),
+                                      make_hexagon_nn_output({1, 1, 1, 1}, sizeof(float))});
+            ins.push_back({.src_id = quant, .output_idx = 0});
+        } else {
+            ins.push_back(operand.hexagon_input);
+        }
+    }
+
+    // add single output node for entire graph
+    bool success = addBasicOperation(OP_OUTPUT, NN_PAD_NA, ins, {});
+    HEXAGON_SOFT_ASSERT(success, "Error adding output operation");
+
+    return true;
+}
+
+void Model::clearModel() {
+    mCompiled = false;
+    for (OperandInfo& operand : mOperands) {
+        operand.hexagon_input = {};
+        operand.hexagon_input_min = {};
+        operand.hexagon_input_max = {};
+        operand.hexagon_output = {};
+    }
+    if (mGraphId != hexagon_nn_nn_id{}) {
+        hexagon::Controller::getInstance().teardown(mGraphId);
+    }
+}
+
+std::vector<bool> Model::supportedOperations() {
+    std::vector<bool> supported(mOperations.size());
+    for (size_t i = 0; i < supported.size(); ++i) {
+        const Operation& operation = mOperations[i];
+        OperationType operationType = operation.type;
+
+        // For now, the operation type is always the same as its first operand
+        // parameter. If this changes in the future, this line of code will need
+        // to be updated.
+        OperandType operandType = mOperands[operation.inputs[0]].type;
+
+        OperationTuple opTuple = std::make_pair(operationType, operandType);
+
+        auto entry = getOperationCheckTable().find(opTuple);
+        if (entry != getOperationCheckTable().end()) {
+            supported[i] = entry->second(operation.inputs, operation.outputs, this);
+        } else {
+            supported[i] = false;
+        }
+    }
+    return supported;
+}
+
+bool Model::prepare() {
+    if (!verifyOperations() || !verifyOperands()) {
+        return false;
+    }
+
+    int err = hexagon::Controller::getInstance().init(&mGraphId);
+    HEXAGON_SOFT_ASSERT_EQ(0, err, "Hexagon could not allocate new graph");
+    HEXAGON_SOFT_ASSERT_NE(0, mGraphId, "Hexagon could not allocate new graph");
+    hexagon::Controller::getInstance().set_debug_level(mGraphId, 0);
+
+    if (!addInputs() || !addOperations() || !addOutputs()) {
+        clearModel();
+        LOG(ERROR) << "Something went wrong. Clearing the model and aborting.";
+        return false;
+    }
+
+    err = hexagon::Controller::getInstance().prepare(mGraphId);
+
+    LOG(INFO) << "PrepareModel was " << (err == 0 ? "SUCCESSFUL" : "UNSUCCESSFUL");
+
+    return err == 0;
+}
+
+static hexagon_nn_tensordef convertToTensordef(const OperandInfo& operand) {
+    std::vector<uint32_t> dimensions = getAlignedDimensions(operand.dimensions, 4);
+    return {
+        .batches = dimensions[0],
+        .height = dimensions[1],
+        .width = dimensions[2],
+        .depth = dimensions[3],
+        .data = operand.buffer,
+        .dataLen = static_cast<int32_t>(operand.length),
+        .data_valid_len = operand.length,  // unused?
+        .unused = 0,
+    };
+}
+
+static uint32_t getSize(const OperandInfo& operand) {
+    return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(),
+                           getSize(operand.type), std::multiplies<>{});
+}
+
+static OperandInfo getUpdatedOperand(const RequestArgument& inputOutput,
+                                     const std::vector<RunTimePoolInfo>& pools,
+                                     const OperandInfo& oldInfo) {
+    OperandInfo newInfo = oldInfo;
+
+    const RunTimePoolInfo& pool = pools[inputOutput.location.poolIndex];
+    uint32_t offset = inputOutput.location.offset;
+
+    if (inputOutput.dimensions.size() > 0) {
+        newInfo.dimensions = inputOutput.dimensions;
+    }
+
+    newInfo.buffer = pool.getBuffer() + offset;
+    newInfo.length = getSize(newInfo);
+
+    return newInfo;
+}
+
+bool Model::execute(const Request& request) {
+    std::vector<RunTimePoolInfo> pools = mapPools(request.pools);
+
+    // prepare inputs
+    std::vector<hexagon_nn_tensordef> inputs;
+    for (size_t i = 0; i < request.inputs.size(); ++i) {
+        const OperandInfo& oldInfo = mOperands[mInputs[i]];
+        OperandInfo newInfo = getUpdatedOperand(request.inputs[i], pools, oldInfo);
+        inputs.push_back(convertToTensordef(newInfo));
+    }
+
+    // prepare outputs
+    std::vector<hexagon_nn_tensordef> outputs;
+    for (size_t i = 0; i < request.outputs.size(); ++i) {
+        const OperandInfo& oldInfo = mOperands[mOutputs[i]];
+        OperandInfo newInfo = getUpdatedOperand(request.outputs[i], pools, oldInfo);
+        outputs.push_back(convertToTensordef(newInfo));
+    }
+
+    // execute model
+    int err = hexagon::Controller::getInstance().execute_new(mGraphId, inputs.data(), inputs.size(),
+                                                             outputs.data(), outputs.size());
+
+    std::for_each(pools.begin(), pools.end(), [](RunTimePoolInfo& pool) { pool.update(); });
+
+    LOG(INFO) << "EXECUTION WAS " << (err == 0 ? "SUCCESSFUL" : "UNSUCCESSFUL");
+
+    return err == 0;
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/HexagonModel.h b/1.0/HexagonModel.h
new file mode 100644
index 0000000..797620b
--- /dev/null
+++ b/1.0/HexagonModel.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_V1_0_HEXAGON_MODEL_H
+#define ANDROID_HARDWARE_V1_0_HEXAGON_MODEL_H
+
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <atomic>
+#include <string>
+#include <vector>
+#include "CpuExecutor.h"
+#include "HexagonController.h"
+#include "HexagonOperations.h"
+#include "HexagonUtils.h"
+#include "OperationsUtils.h"
+#include "hexagon_nn_controller/hexagon_nn_controller.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+using ::android::nn::RunTimePoolInfo;
+using ::android::nn::Shape;
+
+using NeuralnetworksModel = ::android::hardware::neuralnetworks::V1_0::Model;
+
+// runtime operand information
+struct OperandInfo {
+    // tensor information
+    OperandType type;
+    std::vector<uint32_t> dimensions;
+
+    // (optional) quantization paramters
+    float scale;
+    int32_t zeroPoint;
+
+    // lifetime
+    OperandLifeTime lifetime;
+
+    // data location
+    uint8_t* buffer;
+    uint32_t length;
+
+    // Hexagon nnlib identifiers
+    hexagon_nn_input hexagon_input;
+    hexagon_nn_input hexagon_input_min;
+    hexagon_nn_input hexagon_input_max;
+    hexagon_nn_output hexagon_output;
+};
+
+// interface wrapper
+class Model {
+   public:
+    // methods
+    Model() = delete;
+    Model(const Model&) = delete;
+    Model& operator=(const Model&) = delete;
+    Model(Model&& other);
+    Model& operator=(Model&& other);
+
+    Model(const NeuralnetworksModel& model);
+    ~Model();
+
+    std::string getLog();
+    std::string getGraph();
+
+    // model check
+    const int32_t* getPointer(uint32_t operand);
+    Shape getShape(uint32_t operand);
+    bool setShape(uint32_t operand, const Shape& shape);
+    bool isConstant(uint32_t operand);
+
+    // model prepare types
+    const hexagon_nn_input& getTensor(uint32_t operand);
+    const hexagon_nn_input& getQuantizationMin(uint32_t operand);
+    const hexagon_nn_input& getQuantizationMax(uint32_t operand);
+    hexagon_nn_input createQuantizationValue(uint32_t operand, int32_t quant_value);
+    hexagon_nn_input createConvFilterTensor(uint32_t operand);
+    hexagon_nn_input createDepthwiseFilterTensor(uint32_t operand, int32_t depth_multiplier);
+    hexagon_nn_input createFullyConnectedWeightTensor(uint32_t operand);
+    template <typename Type>
+    Type getScalar(uint32_t operand);
+    op_type getFloatActivation(uint32_t operand);
+    op_type getQuantizedActivation(uint32_t operand);
+    hexagon_nn_padding_type getPadding(uint32_t operand);
+
+    template <typename Type>
+    hexagon_nn_input createTensor(uint32_t B, uint32_t H, uint32_t W, uint32_t D,
+                                  const std::vector<Type>& values);
+    hexagon_nn_input createShape(uint32_t B, uint32_t H, uint32_t W, uint32_t D);
+    template <typename Type>
+    hexagon_nn_input createValues(const std::vector<Type>& values);
+    template <typename Type>
+    hexagon_nn_input createScalar(Type value);
+
+    // model prepare operations
+    bool addBasicOperation(op_type op, hexagon_nn_padding_type pad,
+                           const std::vector<hexagon_nn_input>& inputs,
+                           const std::vector<uint32_t>& outputs);
+    bool addFloatOperationWithActivation(op_type op, hexagon_nn_padding_type pad,
+                                         op_type activation,
+                                         const std::vector<hexagon_nn_input>& inputs,
+                                         const std::vector<uint32_t>& outputs);
+    bool addQuant8OperationWithActivation(op_type op, hexagon_nn_padding_type pad,
+                                          op_type activation,
+                                          const std::vector<hexagon_nn_input>& inputs,
+                                          const std::vector<uint32_t>& outputs);
+    bool addFusedFloatOperation(op_type op, hexagon_nn_padding_type pad,
+                                const hexagon_nn_input& bias, op_type activation,
+                                const std::vector<hexagon_nn_input>& inputs,
+                                const std::vector<uint32_t>& outputs);
+    bool addFusedQuant8Operation(op_type op, hexagon_nn_padding_type pad,
+                                 const std::vector<hexagon_nn_input>& bias, op_type activation,
+                                 const std::vector<hexagon_nn_input>& inputs,
+                                 const std::vector<uint32_t>& outputs);
+
+    std::vector<bool> supportedOperations();
+    bool prepare();
+    bool execute(const Request& request);
+
+   private:
+    uint32_t getNextNode();
+    uint32_t addOperationInternal(op_type op, hexagon_nn_padding_type pad,
+                                  const std::vector<hexagon_nn_input>& inputs,
+                                  const std::vector<hexagon_nn_output>& outputs);
+    hexagon_nn_input createTensorInternal(uint32_t B, uint32_t H, uint32_t W, uint32_t D,
+                                          const uint8_t* ptr, size_t size);
+    std::vector<hexagon_nn_input> setupActivationArgs(op_type op);
+    hexagon_nn_input addOperand(uint32_t operand);
+    std::vector<hexagon_nn_output> getHexagonOutputs(const std::vector<uint32_t>& operands);
+    bool registerHexagonInputs(const std::vector<uint32_t>& operands, uint32_t node);
+
+    bool verifyOperations();
+    bool verifyOperands();
+    bool addInputs();
+    bool addOperations();
+    bool addOutputs();
+
+    void clearModel();
+
+    // members
+    hexagon_nn_nn_id mGraphId;
+    uint32_t mNodeCount;
+    bool mCompiled;
+    std::vector<OperandInfo> mOperands;
+    std::vector<Operation> mOperations;
+    std::vector<uint32_t> mInputs;
+    std::vector<uint32_t> mOutputs;
+    std::vector<RunTimePoolInfo> mPools;
+};
+
+// template implementations
+
+template <typename Type>
+Type Model::getScalar(uint32_t operand) {
+    return *reinterpret_cast<const Type*>(mOperands[operand].buffer);
+}
+
+template <typename Type>
+hexagon_nn_input Model::createTensor(uint32_t B, uint32_t H, uint32_t W, uint32_t D,
+                                     const std::vector<Type>& values) {
+    return createTensorInternal(B, H, W, D, reinterpret_cast<const uint8_t*>(values.data()),
+                                values.size() * sizeof(Type));
+}
+
+template <typename Type>
+hexagon_nn_input Model::createValues(const std::vector<Type>& values) {
+    return createTensor(1, 1, 1, values.size(), values);
+}
+
+template <typename Type>
+hexagon_nn_input Model::createScalar(Type value) {
+    return createTensorInternal(1, 1, 1, 1, reinterpret_cast<uint8_t*>(&value), sizeof(Type));
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_V1_0_HEXAGON_MODEL_H
diff --git a/1.0/HexagonOperations.h b/1.0/HexagonOperations.h
new file mode 100644
index 0000000..a19a236
--- /dev/null
+++ b/1.0/HexagonOperations.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_V1_0_HEXAGON_OPERATIONS_H
+#define ANDROID_HARDWARE_V1_0_HEXAGON_OPERATIONS_H
+
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <functional>
+#include <map>
+#include "HexagonUtils.h"
+#include "hexagon_nn_controller/hexagon_nn_controller.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+class Model;
+using HexagonModel = ::android::hardware::neuralnetworks::V1_0::implementation::hexagon::Model;
+
+using ::android::hardware::neuralnetworks::V1_0::Operand;
+
+using OperationTuple = std::pair<OperationType, OperandType>;
+
+using HexagonOperationFn =
+    std::function<bool(const std::vector<uint32_t>& /* ins */,
+                       const std::vector<uint32_t>& /* outs */, HexagonModel* /* model */)>;
+
+using OperationTable = std::map<OperationTuple, HexagonOperationFn>;
+OperationTable& getOperationPrepareTable();
+OperationTable& getOperationCheckTable();
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_V1_0_HEXAGON_OPERATIONS_H
diff --git a/1.0/HexagonOperationsCheck.cpp b/1.0/HexagonOperationsCheck.cpp
new file mode 100644
index 0000000..d900c2b
--- /dev/null
+++ b/1.0/HexagonOperationsCheck.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "HexagonModel.h"
+#include "HexagonOperations.h"
+#include "OperationsUtils.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+using android::nn::Shape;
+
+namespace {
+
+bool addMul(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+            HexagonModel* model, OperationType op) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for " << toString(op));
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << toString(op));
+
+    // get output size
+    const Shape in1Shape = model->getShape(ins[0]);
+    const Shape in2Shape = model->getShape(ins[1]);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(addMulPrepare(in1Shape, in2Shape, &outShape), "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool add(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    return addMul(ins, outs, model, OperationType::ADD);
+}
+
+bool mul(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    return addMul(ins, outs, model, OperationType::MUL);
+}
+
+bool pool(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model,
+          OperationType op) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for " << toString(op));
+
+    // get parameters
+    const Shape inShape = model->getShape(ins[0]);
+
+    // setup parameters
+    int32_t padding_left;
+    int32_t padding_right;
+    int32_t padding_top;
+    int32_t padding_bottom;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+
+    // get parameters
+    if (ins.size() == 10) {
+        padding_left = model->getScalar<int32_t>(ins[1]);
+        padding_right = model->getScalar<int32_t>(ins[2]);
+        padding_top = model->getScalar<int32_t>(ins[3]);
+        padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+
+        HEXAGON_SOFT_ASSERT_NE(getPadding(inShape.dimensions[2], inShape.dimensions[1],
+                                          stride_width, stride_height, filter_width, filter_height,
+                                          padding_left, padding_right, padding_top, padding_bottom),
+                               NN_PAD_NA, "Unknown padding");
+    } else {
+        const int32_t padding_implicit = model->getScalar<int32_t>(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+
+        nn::calculateExplicitPadding(inShape.dimensions[2], stride_width, filter_width,
+                                     padding_implicit, &padding_left, &padding_right);
+        nn::calculateExplicitPadding(inShape.dimensions[1], stride_height, filter_height,
+                                     padding_implicit, &padding_top, &padding_bottom);
+    }
+
+    // get output size
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(
+        genericPoolingPrepare(inShape, padding_left, padding_right, padding_top, padding_bottom,
+                              stride_width, stride_height, filter_width, filter_height, &outShape),
+        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool average_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    return pool(ins, outs, model, OperationType::AVERAGE_POOL_2D);
+}
+
+bool l2_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                HexagonModel* model) {
+    return pool(ins, outs, model, OperationType::L2_POOL_2D);
+}
+
+bool max_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                 HexagonModel* model) {
+    return pool(ins, outs, model, OperationType::MAX_POOL_2D);
+}
+
+bool concatenation(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                   HexagonModel* model) {
+    std::string name = toString(OperationType::CONCATENATION);
+    HEXAGON_SOFT_ASSERT_LE(3, ins.size(), "Need at least 3 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    const size_t numInputTensors = ins.size() - 1;
+
+    const int32_t axis = model->getScalar<int32_t>(ins[numInputTensors]);
+
+    // get output size
+    std::vector<Shape> inShapes(numInputTensors);
+    for (size_t i = 0; i < numInputTensors; ++i) {
+        inShapes[i] = model->getShape(ins[i]);
+    }
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(concatenationPrepare(inShapes, axis, &outShape), "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    std::string name = toString(OperationType::CONV_2D);
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7, "Need 7 or 10 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // setup shapes
+    const Shape inputShape = model->getShape(ins[0]);
+    const Shape filterShape = model->getShape(ins[1]);
+    const Shape biasShape = model->getShape(ins[2]);
+
+    // setup parameters
+    int32_t padding_left;
+    int32_t padding_right;
+    int32_t padding_top;
+    int32_t padding_bottom;
+    int32_t stride_width;
+    int32_t stride_height;
+
+    // get parameters
+    if (ins.size() == 10) {
+        padding_left = model->getScalar<int32_t>(ins[3]);
+        padding_right = model->getScalar<int32_t>(ins[4]);
+        padding_top = model->getScalar<int32_t>(ins[5]);
+        padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+
+        HEXAGON_SOFT_ASSERT_NE(
+            getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                       stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                       padding_left, padding_right, padding_top, padding_bottom),
+            NN_PAD_NA, "Unknown padding");
+    } else {
+        const int32_t padding_implicit = model->getScalar<int32_t>(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+
+        nn::calculateExplicitPadding(inputShape.dimensions[2], stride_width,
+                                     filterShape.dimensions[2], padding_implicit, &padding_left,
+                                     &padding_right);
+        nn::calculateExplicitPadding(inputShape.dimensions[1], stride_height,
+                                     filterShape.dimensions[1], padding_implicit, &padding_top,
+                                     &padding_bottom);
+    }
+
+    // get output size
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(
+        convPrepare(inputShape, filterShape, biasShape, padding_left, padding_right, padding_top,
+                    padding_bottom, stride_width, stride_height, &outShape),
+        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    // enforce filter is a constant
+    HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << "requires filter to be constant data");
+
+    return true;
+}
+
+bool depthwise_conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                       HexagonModel* model) {
+    std::string name = toString(OperationType::DEPTHWISE_CONV_2D);
+    HEXAGON_SOFT_ASSERT(ins.size() == 8 || ins.size() == 11, "Need 8 or 11 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // setup shapes
+    const Shape inputShape = model->getShape(ins[0]);
+    const Shape filterShape = model->getShape(ins[1]);
+    const Shape biasShape = model->getShape(ins[2]);
+
+    // setup parameters
+    int32_t padding_left;
+    int32_t padding_right;
+    int32_t padding_top;
+    int32_t padding_bottom;
+    int32_t stride_width;
+    int32_t stride_height;
+
+    // get parameters
+    if (ins.size() == 11) {
+        padding_left = model->getScalar<int32_t>(ins[3]);
+        padding_right = model->getScalar<int32_t>(ins[4]);
+        padding_top = model->getScalar<int32_t>(ins[5]);
+        padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+
+        HEXAGON_SOFT_ASSERT_NE(
+            getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                       stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                       padding_left, padding_right, padding_top, padding_bottom),
+            NN_PAD_NA, "Unknown padding");
+
+    } else {
+        const int32_t padding_implicit = model->getScalar<int32_t>(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+
+        nn::calculateExplicitPadding(inputShape.dimensions[2], stride_width,
+                                     filterShape.dimensions[2], padding_implicit, &padding_left,
+                                     &padding_right);
+        nn::calculateExplicitPadding(inputShape.dimensions[1], stride_height,
+                                     filterShape.dimensions[1], padding_implicit, &padding_top,
+                                     &padding_bottom);
+    }
+
+    // get output size
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(
+        depthwiseConvPrepare(inputShape, filterShape, biasShape, padding_left, padding_right,
+                             padding_top, padding_bottom, stride_width, stride_height, &outShape),
+        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    // enforce filter is a constant
+    HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << " requires filter to be constant data");
+
+    return true;
+}
+
+bool dequantize(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                HexagonModel* model) {
+    std::string name = toString(OperationType::DEQUANTIZE);
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // get output size
+    const Shape inputShape = model->getShape(ins[0]);
+    Shape outShape = model->getShape(outs[0]);
+
+    HEXAGON_SOFT_ASSERT(dequantizePrepare(inputShape, &outShape), "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool fully_connected(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    std::string name = toString(OperationType::FULLY_CONNECTED);
+    HEXAGON_SOFT_ASSERT_EQ(4, ins.size(), "Need 4 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // get output size
+    const Shape inputShape = model->getShape(ins[0]);
+    const Shape weightsShape = model->getShape(ins[1]);
+    const Shape biasShape = model->getShape(ins[2]);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(fullyConnectedPrepare(inputShape, weightsShape, biasShape, &outShape),
+                        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    // enforce weight is a constant
+    HEXAGON_SOFT_ASSERT(model->isConstant(ins[1]), name << "requires weight to be constant data");
+
+    return true;
+}
+
+bool local_response_normalization(const std::vector<uint32_t>& ins,
+                                  const std::vector<uint32_t>& outs, HexagonModel* model) {
+    std::string name = toString(OperationType::LOCAL_RESPONSE_NORMALIZATION);
+    HEXAGON_SOFT_ASSERT_EQ(5, ins.size(), "Need 5 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // get output size
+    const Shape inShape = model->getShape(ins[0]);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(genericNormalizationPrepare(inShape, &outShape), "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool activation(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                HexagonModel* model, uint32_t numInputs, OperationType op) {
+    HEXAGON_SOFT_ASSERT_EQ(numInputs, ins.size(),
+                           "Need " << numInputs << " input for " << toString(op));
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << toString(op));
+
+    // get output size
+    const Shape inShape = model->getShape(ins[0]);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(genericActivationPrepare(inShape, &outShape), "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool logistic(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+              HexagonModel* model) {
+    return activation(ins, outs, model, 1, OperationType::LOGISTIC);
+}
+
+bool relu(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+          HexagonModel* model) {
+    return activation(ins, outs, model, 1, OperationType::RELU);
+}
+
+bool relu1(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    return activation(ins, outs, model, 1, OperationType::RELU1);
+}
+
+bool relu6(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    return activation(ins, outs, model, 1, OperationType::RELU6);
+}
+
+bool softmax(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    return activation(ins, outs, model, 2, OperationType::SOFTMAX);
+}
+
+bool tanh(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+          HexagonModel* model) {
+    return activation(ins, outs, model, 1, OperationType::TANH);
+}
+
+bool reshape(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    std::string name = toString(OperationType::RESHAPE);
+    HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // get output size
+    const Shape inShape = model->getShape(ins[0]);
+    const Shape targetShape = model->getShape(ins[1]);
+    const int32_t* targetShapePtr = model->getPointer(ins[1]);
+    int32_t targetShapeNumElem = ::android::nn::getNumberOfElements(targetShape);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(targetShapePtr != nullptr, "pointer value is currently nullptr");
+
+    HEXAGON_SOFT_ASSERT(reshapePrepare(inShape, targetShapePtr, targetShapeNumElem, &outShape),
+                        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+bool resize_bilinear(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    std::string name = toString(OperationType::RESIZE_BILINEAR);
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for " << name);
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for " << name);
+
+    // get parameters
+    const int32_t width = model->getScalar<int32_t>(ins[1]);
+    const int32_t height = model->getScalar<int32_t>(ins[2]);
+
+    // get output size
+    const Shape inShape = model->getShape(ins[0]);
+    Shape outShape = model->getShape(outs[0]);
+    HEXAGON_SOFT_ASSERT(resizeBilinearPrepare(inShape, width, height, &outShape),
+                        "Error getting shape");
+    HEXAGON_SOFT_ASSERT(model->setShape(outs[0], outShape), "Error setting shape");
+
+    return true;
+}
+
+}  // namespace
+
+OperationTable& getOperationCheckTable() {
+    static OperationTable table = {
+        // NOTE: the operations that are commented out via inline represent
+        // operations that are valid for the Android O NNAPI release, but are
+        // currently not implemented in HVX.
+
+        // -------------------------- 32-BIT FLOAT ----------------------------
+        // HVX is only performant when running on quantized values. Further, as
+        // an optimization, the current HVX driver will convert some floating
+        // point tensors into quantized values, perform the operation, and then
+        // convert them back to floating point. This results in a loss in
+        // precision causing some tests to fail. For these reasons, the FLOAT32
+        // operations are being temporarily disabled.
+        /*
+        {{OperationType::ADD, OperandType::TENSOR_FLOAT32}, add},
+        {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_FLOAT32}, average_pool_2d},
+        {{OperationType::CONCATENATION, OperandType::TENSOR_FLOAT32}, concatenation},
+        {{OperationType::CONV_2D, OperandType::TENSOR_FLOAT32}, conv_2d},
+        {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_FLOAT32}, depthwise_conv_2d},
+        //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_FLOAT32}, depth_to_space},
+        //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_FLOAT32}, embedding_lookup},
+        //{{OperationType::FLOOR, OperandType::TENSOR_FLOAT32}, floor},
+        {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_FLOAT32}, fully_connected},
+        //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_FLOAT32}, hashtable_lookup},
+        //{{OperationType::L2_NORMALIZATION, OperandType::TENSOR_FLOAT32}, l2_normalization},
+        {{OperationType::L2_POOL_2D, OperandType::TENSOR_FLOAT32}, l2_pool_2d},
+        {{OperationType::LOCAL_RESPONSE_NORMALIZATION, OperandType::TENSOR_FLOAT32},
+          local_response_normalization},
+        {{OperationType::LOGISTIC, OperandType::TENSOR_FLOAT32}, logistic},
+        //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_FLOAT32}, lsh_projection},
+        //{{OperationType::LSTM, OperandType::TENSOR_FLOAT32}, lstm },
+        {{OperationType::MAX_POOL_2D, OperandType::TENSOR_FLOAT32}, max_pool_2d},
+        {{OperationType::MUL, OperandType::TENSOR_FLOAT32}, mul},
+        {{OperationType::RELU, OperandType::TENSOR_FLOAT32}, relu},
+        {{OperationType::RELU1, OperandType::TENSOR_FLOAT32}, relu1},
+        {{OperationType::RELU6, OperandType::TENSOR_FLOAT32}, relu6},
+        {{OperationType::RESHAPE, OperandType::TENSOR_FLOAT32}, reshape},
+        {{OperationType::RESIZE_BILINEAR, OperandType::TENSOR_FLOAT32}, resize_bilinear},
+        //{{OperationType::RNN, OperandType::TENSOR_FLOAT32}, rnn},
+        {{OperationType::SOFTMAX, OperandType::TENSOR_FLOAT32}, softmax},
+        //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_FLOAT32}, space_to_depth},
+        //{{OperationType::SVDF, OperandType::TENSOR_FLOAT32}, svdf },
+        {{OperationType::TANH, OperandType::TENSOR_FLOAT32}, tanh},
+        */
+
+        // -------------------- QUANTIZED 8-BIT ASYMMETRICAL ------------------
+        {{OperationType::ADD, OperandType::TENSOR_QUANT8_ASYMM}, add},
+        {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM}, average_pool_2d},
+        {{OperationType::CONCATENATION, OperandType::TENSOR_QUANT8_ASYMM}, concatenation},
+        {{OperationType::CONV_2D, OperandType::TENSOR_QUANT8_ASYMM}, conv_2d},
+        {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_QUANT8_ASYMM}, depthwise_conv_2d},
+        //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_QUANT8_ASYMM}, depth_to_space},
+        {{OperationType::DEQUANTIZE, OperandType::TENSOR_QUANT8_ASYMM}, dequantize},
+        //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM}, embedding_lookup},
+        {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_QUANT8_ASYMM}, fully_connected},
+        //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM}, hashtable_lookup},
+        {{OperationType::LOGISTIC, OperandType::TENSOR_QUANT8_ASYMM}, logistic},
+        //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_QUANT8_ASYMM}, lsh_projection},
+        {{OperationType::MAX_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM}, max_pool_2d},
+        {{OperationType::MUL, OperandType::TENSOR_QUANT8_ASYMM}, mul},
+        {{OperationType::RELU, OperandType::TENSOR_QUANT8_ASYMM}, relu},
+        {{OperationType::RELU1, OperandType::TENSOR_QUANT8_ASYMM}, relu1},
+        {{OperationType::RELU6, OperandType::TENSOR_QUANT8_ASYMM}, relu6},
+        {{OperationType::RESHAPE, OperandType::TENSOR_QUANT8_ASYMM}, reshape},
+        {{OperationType::SOFTMAX, OperandType::TENSOR_QUANT8_ASYMM}, softmax},
+        //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_QUANT8_ASYMM}, space_to_depth},
+    };
+
+    // The following functions are normally used by float32, but those
+    // operations have been temporarily disabled. Void explicitly marks them as
+    // unused, and prevents the compiler from throwing an error.
+    (void)l2_pool_2d;
+    (void)local_response_normalization;
+    (void)tanh;
+    (void)resize_bilinear;
+
+    return table;
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/HexagonOperationsPrepare.cpp b/1.0/HexagonOperationsPrepare.cpp
new file mode 100644
index 0000000..3b8238f
--- /dev/null
+++ b/1.0/HexagonOperationsPrepare.cpp
@@ -0,0 +1,1025 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "HexagonModel.h"
+#include "HexagonOperations.h"
+#include "OperationsUtils.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+using android::nn::Shape;
+
+namespace {
+namespace float32 {
+
+bool add(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for float32::add");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::add");
+
+    // get parameters
+    const hexagon_nn_input& in1 = model->getTensor(ins[0]);
+    const hexagon_nn_input& in2 = model->getTensor(ins[1]);
+
+    const op_type act = model->getFloatActivation(ins[2]);
+
+    // add node to graph
+    return model->addFusedFloatOperation(OP_Add_f, NN_PAD_NA, {}, act, {in1, in2}, outs);
+}
+
+bool average_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for float32::average_pool_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::average_pool_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[1]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[2]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getFloatActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filter_width, filter_height, padding_left, padding_right,
+                         padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getFloatActivation(ins[6]);
+    }
+
+    const hexagon_nn_input window = model->createShape(1, filter_height, filter_width, 1);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFloatOperationWithActivation(OP_AvgPool_f, pad, act, {input, window, stride},
+                                                  outs);
+}
+
+bool concatenation(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                   HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_LE(3, ins.size(), "Need at least 3 inputs for float32::concatenation");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::concatenation");
+
+    const size_t numInputTensors = ins.size() - 1;
+
+    // get parameters
+    std::vector<hexagon_nn_input> inputs(numInputTensors + 1);
+    for (size_t i = 0; i < numInputTensors; ++i) {
+        inputs[i + 1] = model->getTensor(ins[i]);
+    }
+
+    // axis being concatenated
+    const int32_t axis = model->getScalar<int32_t>(ins[numInputTensors]);
+    const int32_t dims = model->getShape(ins[0]).dimensions.size();
+    inputs[0] = model->createScalar<int32_t>(axis + (4 - dims));
+
+    // add node to graph
+    return model->addBasicOperation(OP_Concat_f, NN_PAD_NA, inputs, outs);
+}
+
+bool conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for float32::conv_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::conv_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input filter = model->createConvFilterTensor(ins[1]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[4]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[5]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getFloatActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        const Shape filterShape = model->getShape(ins[1]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                         padding_left, padding_right, padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getFloatActivation(ins[6]);
+    }
+
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFusedFloatOperation(OP_Conv2d_f, pad, bias, act, {input, filter, stride},
+                                         outs);
+}
+
+bool depthwise_conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                       HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 11 || ins.size() == 8,
+                        "Need 8 or 11 inputs for float32::depthwise_conv_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::depthwise_conv_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    const Shape filterShape = model->getShape(ins[1]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t depth_multiplier;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 11) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[4]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[5]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+        depth_multiplier = model->getScalar<int32_t>(ins[9]);
+        act = model->getFloatActivation(ins[10]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        const Shape filterShape = model->getShape(ins[1]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                         padding_left, padding_right, padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+        depth_multiplier = model->getScalar<int32_t>(ins[6]);
+        act = model->getFloatActivation(ins[7]);
+    }
+
+    const hexagon_nn_input filter = model->createDepthwiseFilterTensor(ins[1], depth_multiplier);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFusedFloatOperation(OP_DepthwiseConv2d_f, pad, bias, act,
+                                         {input, filter, stride}, outs);
+}
+
+bool fully_connected(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(4, ins.size(), "Need 4 inputs for float32::fully_connected");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::fully_connected");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& weights = model->createFullyConnectedWeightTensor(ins[1]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    const op_type act = model->getFloatActivation(ins[3]);
+
+    // add node to graph
+    return model->addFusedFloatOperation(OP_MatMul_f, NN_PAD_NA, bias, act, {input, weights}, outs);
+}
+
+bool l2_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for float32::l2_pool_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::l2_pool_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[1]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[2]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getFloatActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filter_width, filter_height, padding_left, padding_right,
+                         padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getFloatActivation(ins[6]);
+    }
+
+    const hexagon_nn_input window = model->createShape(1, filter_height, filter_width, 1);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFloatOperationWithActivation(OP_L2Pool_f, pad, act, {input, window, stride},
+                                                  outs);
+}
+
+bool local_response_normalization(const std::vector<uint32_t>& ins,
+                                  const std::vector<uint32_t>& outs, HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(5, ins.size(),
+                           "Need 5 inputs for float32::local_response_normalization");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(),
+                           "Need 1 output for float32::local_response_normalization");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+    const hexagon_nn_input& alpha = model->getTensor(ins[3]);
+    const hexagon_nn_input& beta = model->getTensor(ins[4]);
+
+    // create value that's [1, 1, 1, radius] with value of 1.0f
+    const int32_t radius = model->getScalar<int32_t>(ins[1]);
+    const hexagon_nn_input window = model->createTensor<float>(1, 1, 1, radius * 2 + 1, {1.0f});
+
+    // add node to graph
+    return model->addBasicOperation(OP_LRN_f, NN_PAD_NA, {input, window, bias, alpha, beta}, outs);
+}
+
+bool logistic(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+              HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for float32::logistic");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::logistic");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Sigmoid_f, NN_PAD_NA, {input}, outs);
+}
+
+bool max_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                 HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for float32::max_pool_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::max_pool_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[1]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[2]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getFloatActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filter_width, filter_height, padding_left, padding_right,
+                         padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getFloatActivation(ins[6]);
+    }
+
+    const hexagon_nn_input window = model->createShape(1, filter_height, filter_width, 1);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFloatOperationWithActivation(OP_MaxPool_f, pad, act, {input, window, stride},
+                                                  outs);
+}
+
+bool mul(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for float32::mul");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::mul");
+
+    // get parameters
+    const hexagon_nn_input& in1 = model->getTensor(ins[0]);
+    const hexagon_nn_input& in2 = model->getTensor(ins[1]);
+
+    const op_type act = model->getFloatActivation(ins[2]);
+
+    // add node to graph
+    return model->addFusedFloatOperation(OP_Mul_f, NN_PAD_NA, {}, act, {in1, in2}, outs);
+}
+
+bool relu(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+          HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for float32::relu");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::relu");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Relu_f, NN_PAD_NA, {input}, outs);
+}
+
+bool relu1(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for float32::relu1");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::relu1");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input min = model->createScalar(-1.0f);
+    const hexagon_nn_input max = model->createScalar(1.0f);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Clamp_f, NN_PAD_NA, {input, min, max}, outs);
+}
+
+bool relu6(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for float32::relu6");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::relu6");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input max = model->createScalar(6.0f);
+
+    // add node to graph
+    return model->addBasicOperation(OP_ReluX_f, NN_PAD_NA, {input, max}, outs);
+}
+
+bool reshape(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for float32::reshape");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::reshape");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& newdims = model->getTensor(ins[1]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Reshape, NN_PAD_NA, {input, newdims}, outs);
+}
+
+bool resize_bilinear(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for float32::resize_bilinear");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::resize_bilinear");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    const int32_t width = model->getScalar<int32_t>(ins[1]);
+    const int32_t height = model->getScalar<int32_t>(ins[2]);
+
+    const hexagon_nn_input newdim = model->createValues<int32_t>({height, width});
+
+    // add node to graph
+    return model->addBasicOperation(OP_ResizeBilinear_f, NN_PAD_NA, {input, newdim}, outs);
+}
+
+bool softmax(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for float32::softmax");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::softmax");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& beta = model->getTensor(ins[1]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Softmax_f, NN_PAD_NA, {input, beta}, outs);
+}
+
+bool tanh(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+          HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for float32::tanh");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for float32::tanh");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Tanh_f, NN_PAD_NA, {input}, outs);
+}
+
+}  // namespace float32
+
+namespace quant8_asym {
+
+bool add(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for quant8_asym::add");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::add");
+
+    // get parameters
+    const hexagon_nn_input& in1 = model->getTensor(ins[0]);
+    const hexagon_nn_input& in2 = model->getTensor(ins[1]);
+
+    const op_type act = model->getQuantizedActivation(ins[2]);
+
+    const hexagon_nn_input& in1_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& in1_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input& in2_min = model->getQuantizationMin(ins[1]);
+    const hexagon_nn_input& in2_max = model->getQuantizationMax(ins[1]);
+
+    // add node to graph
+    return model->addFusedQuant8Operation(OP_QuantizedAdd_8p8to32, NN_PAD_NA, {}, act,
+                                          {in1, in2, in1_min, in1_max, in2_min, in2_max}, outs);
+}
+
+bool average_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for quant8_asym::average_pool_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::average_pool_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[1]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[2]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getQuantizedActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filter_width, filter_height, padding_left, padding_right,
+                         padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getQuantizedActivation(ins[6]);
+    }
+
+    const hexagon_nn_input& in_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& in_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input window = model->createShape(1, filter_height, filter_width, 1);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addQuant8OperationWithActivation(OP_QuantizedAvgPool_8, pad, act,
+                                                   {input, in_min, in_max, window, stride}, outs);
+}
+
+bool concatenation(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                   HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_LE(3, ins.size(), "Need at least 3 inputs for quant8_asym::concatenation");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::concatenation");
+
+    const size_t numInputTensors = ins.size() - 1;
+
+    // get parameters
+    std::vector<hexagon_nn_input> inputs(numInputTensors * 3 + 1);
+    for (size_t i = 0; i < numInputTensors; ++i) {
+        inputs[i + 1 + numInputTensors * 0] = model->getTensor(ins[i]);
+        inputs[i + 1 + numInputTensors * 1] = model->getQuantizationMin(ins[i]);
+        inputs[i + 1 + numInputTensors * 2] = model->getQuantizationMax(ins[i]);
+    }
+
+    // axis being concatenated
+    const int32_t axis = model->getScalar<int32_t>(ins[numInputTensors]);
+    const int32_t dims = model->getShape(ins[0]).dimensions.size();
+    inputs[0] = model->createScalar<int32_t>(axis + (4 - dims));
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedConcat_8, NN_PAD_NA, inputs, outs);
+}
+
+bool conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for quant8_asym::conv_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::conv_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input filter = model->createConvFilterTensor(ins[1]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[4]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[5]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getQuantizedActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        const Shape filterShape = model->getShape(ins[1]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                         padding_left, padding_right, padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getQuantizedActivation(ins[6]);
+    }
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input& filter_min = model->getQuantizationMin(ins[1]);
+    const hexagon_nn_input& filter_max = model->getQuantizationMax(ins[1]);
+    const hexagon_nn_input& bias_min = model->getQuantizationMin(ins[2]);
+    const hexagon_nn_input& bias_max = model->getQuantizationMax(ins[2]);
+
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFusedQuant8Operation(
+        OP_QuantizedConv2d_8x8to32, pad, {bias, bias_min, bias_max}, act,
+        {input, filter, input_min, input_max, filter_min, filter_max, stride}, outs);
+}
+
+bool depthwise_conv_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                       HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 11 || ins.size() == 8,
+                        "Need 8 to 11 inputs for quant8_asym::depthwise_conv_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::depthwise_conv_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t depth_multiplier;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 11) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[4]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[5]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[6]);
+        stride_width = model->getScalar<int32_t>(ins[7]);
+        stride_height = model->getScalar<int32_t>(ins[8]);
+        depth_multiplier = model->getScalar<int32_t>(ins[9]);
+        act = model->getQuantizedActivation(ins[10]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        const Shape filterShape = model->getShape(ins[1]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filterShape.dimensions[2], filterShape.dimensions[1],
+                         padding_left, padding_right, padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[3]);
+        stride_width = model->getScalar<int32_t>(ins[4]);
+        stride_height = model->getScalar<int32_t>(ins[5]);
+        depth_multiplier = model->getScalar<int32_t>(ins[6]);
+        act = model->getQuantizedActivation(ins[7]);
+    }
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input& filter_min = model->getQuantizationMin(ins[1]);
+    const hexagon_nn_input& filter_max = model->getQuantizationMax(ins[1]);
+    const hexagon_nn_input& bias_min = model->getQuantizationMin(ins[2]);
+    const hexagon_nn_input& bias_max = model->getQuantizationMax(ins[2]);
+
+    const hexagon_nn_input filter = model->createDepthwiseFilterTensor(ins[1], depth_multiplier);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addFusedQuant8Operation(
+        OP_QuantizedDepthwiseConv2d_8x8to32, pad, {bias, bias_min, bias_max}, act,
+        {input, filter, input_min, input_max, filter_min, filter_max, stride}, outs);
+}
+
+bool dequantize(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for quant8_asym::dequantize");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::dequantize");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_Dequantize, NN_PAD_NA, {input, input_min, input_max}, outs);
+}
+
+bool fully_connected(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                     HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(4, ins.size(), "Need 4 inputs for quant8::fully_connected");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8::fully_connected");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& weights = model->createFullyConnectedWeightTensor(ins[1]);
+    const hexagon_nn_input& bias = model->getTensor(ins[2]);
+
+    const op_type act = model->getQuantizedActivation(ins[3]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input& weights_min = model->getQuantizationMin(ins[1]);
+    const hexagon_nn_input& weights_max = model->getQuantizationMax(ins[1]);
+    const hexagon_nn_input& bias_min = model->getQuantizationMin(ins[2]);
+    const hexagon_nn_input& bias_max = model->getQuantizationMax(ins[2]);
+
+    // add node to graph
+    return model->addFusedQuant8Operation(
+        OP_QuantizedMatMul_8x8to32, NN_PAD_NA, {bias, bias_min, bias_max}, act,
+        {input, weights, input_min, input_max, weights_min, weights_max}, outs);
+}
+
+bool logistic(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+              HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for quant8_asym::logistic");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::logistic");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+
+    // TFLite uses different max value
+    const hexagon_nn_input input_max = model->createQuantizationValue(ins[0], 256);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedSigmoid_8, NN_PAD_NA, {input, input_min, input_max},
+                                    outs);
+}
+
+bool max_pool_2d(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+                 HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT(ins.size() == 10 || ins.size() == 7,
+                        "Need 7 or 10 inputs for quant8_asym::max_pool_2d");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::max_pool_2d");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    // setup parameters
+    hexagon_nn_padding_type pad;
+    int32_t stride_width;
+    int32_t stride_height;
+    int32_t filter_width;
+    int32_t filter_height;
+    op_type act;
+
+    // get parameters
+    if (ins.size() == 10) {
+        const int32_t padding_left = model->getScalar<int32_t>(ins[1]);
+        const int32_t padding_right = model->getScalar<int32_t>(ins[2]);
+        const int32_t padding_top = model->getScalar<int32_t>(ins[3]);
+        const int32_t padding_bottom = model->getScalar<int32_t>(ins[4]);
+        stride_width = model->getScalar<int32_t>(ins[5]);
+        stride_height = model->getScalar<int32_t>(ins[6]);
+        filter_width = model->getScalar<int32_t>(ins[7]);
+        filter_height = model->getScalar<int32_t>(ins[8]);
+        act = model->getQuantizedActivation(ins[9]);
+
+        const Shape inputShape = model->getShape(ins[0]);
+        pad = getPadding(inputShape.dimensions[2], inputShape.dimensions[1], stride_width,
+                         stride_height, filter_width, filter_height, padding_left, padding_right,
+                         padding_top, padding_bottom);
+        HEXAGON_SOFT_ASSERT_NE(pad, NN_PAD_NA, "Unknown padding");
+    } else {
+        pad = model->getPadding(ins[1]);
+        stride_width = model->getScalar<int32_t>(ins[2]);
+        stride_height = model->getScalar<int32_t>(ins[3]);
+        filter_width = model->getScalar<int32_t>(ins[4]);
+        filter_height = model->getScalar<int32_t>(ins[5]);
+        act = model->getQuantizedActivation(ins[6]);
+    }
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input window = model->createShape(1, filter_height, filter_width, 1);
+    const hexagon_nn_input stride = model->createShape(1, stride_height, stride_width, 1);
+
+    // add node to graph
+    return model->addQuant8OperationWithActivation(
+        OP_QuantizedMaxPool_8, pad, act, {input, input_min, input_max, window, stride}, outs);
+}
+
+bool mul(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs, HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(3, ins.size(), "Need 3 inputs for quant8_asym::mul");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::mul");
+
+    // get parameters
+    const hexagon_nn_input& in1 = model->getTensor(ins[0]);
+    const hexagon_nn_input& in2 = model->getTensor(ins[1]);
+
+    const op_type act = model->getQuantizedActivation(ins[2]);
+
+    const hexagon_nn_input& in1_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& in1_max = model->getQuantizationMax(ins[0]);
+    const hexagon_nn_input& in2_min = model->getQuantizationMin(ins[1]);
+    const hexagon_nn_input& in2_max = model->getQuantizationMax(ins[1]);
+
+    // add node to graph
+    return model->addFusedQuant8Operation(OP_QuantizedMul_8x8to32, NN_PAD_NA, {}, act,
+                                          {in1, in2, in1_min, in1_max, in2_min, in2_max}, outs);
+}
+
+bool relu(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+          HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for quant8_asym::relu");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::relu");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedRelu_8, NN_PAD_NA, {input, input_min, input_max},
+                                    outs);
+}
+
+bool relu1(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for quant8_asym::relu1");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::relu1");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input min = model->createScalar(-1.0f);
+    const hexagon_nn_input max = model->createScalar(1.0f);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedClamp_8, NN_PAD_NA,
+                                    {input, input_min, input_max, min, max}, outs);
+}
+
+bool relu6(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+           HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(1, ins.size(), "Need 1 input for quant8_asym::relu6");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::relu6");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input max = model->createScalar(6.0f);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedReluX_8, NN_PAD_NA,
+                                    {input, input_min, input_max, max}, outs);
+}
+
+bool reshape(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for quant8_asym::reshape");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::reshape");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& newdims = model->getTensor(ins[1]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedReshape, NN_PAD_NA,
+                                    {input, newdims, input_min, input_max}, outs);
+}
+
+bool softmax(const std::vector<uint32_t>& ins, const std::vector<uint32_t>& outs,
+             HexagonModel* model) {
+    HEXAGON_SOFT_ASSERT_EQ(2, ins.size(), "Need 2 inputs for quant8_asym::softmax");
+    HEXAGON_SOFT_ASSERT_EQ(1, outs.size(), "Need 1 output for quant8_asym::softmax");
+
+    // get parameters
+    const hexagon_nn_input& input = model->getTensor(ins[0]);
+    const hexagon_nn_input& beta = model->getTensor(ins[1]);
+
+    const hexagon_nn_input& input_min = model->getQuantizationMin(ins[0]);
+    const hexagon_nn_input& input_max = model->getQuantizationMax(ins[0]);
+
+    // add node to graph
+    return model->addBasicOperation(OP_QuantizedSoftmax_8, NN_PAD_NA,
+                                    {input, input_min, input_max, beta}, outs);
+}
+
+}  // namespace quant8_asym
+
+}  // namespace
+
+OperationTable& getOperationPrepareTable() {
+    static OperationTable table = {
+        // NOTE: the operations that are commented out via inline represent
+        // operations that are valid for the Android O NNAPI release, but are
+        // currently not implemented in HVX.
+
+        // -------------------------- 32-BIT FLOAT ----------------------------
+        // HVX is only performant when running on quantized values. Further, as
+        // an optimization, the current HVX driver will convert some floating
+        // point tensors into quantized values, perform the operation, and then
+        // convert them back to floating point. This results in a loss in
+        // precision causing some tests to fail. For these reasons, the FLOAT32
+        // operations are being temporarily disabled.
+        /*
+        {{OperationType::ADD, OperandType::TENSOR_FLOAT32}, float32::add},
+        {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_FLOAT32}, float32::average_pool_2d},
+        {{OperationType::CONCATENATION, OperandType::TENSOR_FLOAT32}, float32::concatenation},
+        {{OperationType::CONV_2D, OperandType::TENSOR_FLOAT32}, float32::conv_2d},
+        {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_FLOAT32},
+          float32::depthwise_conv_2d},
+        //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_FLOAT32}, float32::depth_to_space},
+        //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_FLOAT32},
+        //  float32::embedding_lookup},
+        //{{OperationType::FLOOR, OperandType::TENSOR_FLOAT32}, float32::floor},
+        {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_FLOAT32}, float32::fully_connected},
+        //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_FLOAT32},
+        //  float32::hashtable_lookup},
+        //{{OperationType::L2_NORMALIZATION, OperandType::TENSOR_FLOAT32},
+        //  float32::l2_normalization},
+        {{OperationType::L2_POOL_2D, OperandType::TENSOR_FLOAT32}, float32::l2_pool_2d},
+        {{OperationType::LOCAL_RESPONSE_NORMALIZATION, OperandType::TENSOR_FLOAT32},
+          float32::local_response_normalization},
+        {{OperationType::LOGISTIC, OperandType::TENSOR_FLOAT32}, float32::logistic},
+        //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_FLOAT32}, float32::lsh_projection},
+        //{{OperationType::LSTM, OperandType::TENSOR_FLOAT32}, float32::lstm },
+        {{OperationType::MAX_POOL_2D, OperandType::TENSOR_FLOAT32}, float32::max_pool_2d},
+        {{OperationType::MUL, OperandType::TENSOR_FLOAT32}, float32::mul},
+        {{OperationType::RELU, OperandType::TENSOR_FLOAT32}, float32::relu},
+        {{OperationType::RELU1, OperandType::TENSOR_FLOAT32}, float32::relu1},
+        {{OperationType::RELU6, OperandType::TENSOR_FLOAT32}, float32::relu6},
+        {{OperationType::RESHAPE, OperandType::TENSOR_FLOAT32}, float32::reshape},
+        {{OperationType::RESIZE_BILINEAR, OperandType::TENSOR_FLOAT32}, float32::resize_bilinear},
+        //{{OperationType::RNN, OperandType::TENSOR_FLOAT32}, float32::rnn},
+        {{OperationType::SOFTMAX, OperandType::TENSOR_FLOAT32}, float32::softmax},
+        //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_FLOAT32}, float32::space_to_depth},
+        //{{OperationType::SVDF, OperandType::TENSOR_FLOAT32}, float32::svdf },
+        {{OperationType::TANH, OperandType::TENSOR_FLOAT32}, float32::tanh},
+        */
+
+        // -------------------- QUANTIZED 8-BIT ASYMMETRICAL ------------------
+        {{OperationType::ADD, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::add},
+        {{OperationType::AVERAGE_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM},
+         quant8_asym::average_pool_2d},
+        {{OperationType::CONCATENATION, OperandType::TENSOR_QUANT8_ASYMM},
+         quant8_asym::concatenation},
+        {{OperationType::CONV_2D, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::conv_2d},
+        {{OperationType::DEPTHWISE_CONV_2D, OperandType::TENSOR_QUANT8_ASYMM},
+         quant8_asym::depthwise_conv_2d},
+        //{{OperationType::DEPTH_TO_SPACE, OperandType::TENSOR_QUANT8_ASYMM},
+        //  quant8_asym::depth_to_space},
+        {{OperationType::DEQUANTIZE, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::dequantize},
+        //{{OperationType::EMBEDDING_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM},
+        //  quant8_asym::embedding_lookup},
+        {{OperationType::FULLY_CONNECTED, OperandType::TENSOR_QUANT8_ASYMM},
+         quant8_asym::fully_connected},
+        //{{OperationType::HASHTABLE_LOOKUP, OperandType::TENSOR_QUANT8_ASYMM},
+        //  quant8_asym::hashtable_lookup},
+        {{OperationType::LOGISTIC, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::logistic},
+        //{{OperationType::LSH_PROJECTION, OperandType::TENSOR_QUANT8_ASYMM},
+        //  quant8_asym::lsh_projection},
+        {{OperationType::MAX_POOL_2D, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::max_pool_2d},
+        {{OperationType::MUL, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::mul},
+        {{OperationType::RELU, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::relu},
+        {{OperationType::RELU1, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::relu1},
+        {{OperationType::RELU6, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::relu6},
+        {{OperationType::RESHAPE, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::reshape},
+        {{OperationType::SOFTMAX, OperandType::TENSOR_QUANT8_ASYMM}, quant8_asym::softmax},
+        //{{OperationType::SPACE_TO_DEPTH, OperandType::TENSOR_QUANT8_ASYMM},
+        //  quant8_asym::space_to_depth},
+    };
+
+    // The following functions are normally used by float32, but those
+    // operations have been temporarily disabled. Void explicitly marks them as
+    // unused, and prevents the compiler from throwing an error.
+    (void)float32::add;
+    (void)float32::average_pool_2d;
+    (void)float32::concatenation;
+    (void)float32::conv_2d;
+    (void)float32::depthwise_conv_2d;
+    (void)float32::fully_connected;
+    (void)float32::l2_pool_2d;
+    (void)float32::local_response_normalization;
+    (void)float32::logistic;
+    (void)float32::max_pool_2d;
+    (void)float32::mul;
+    (void)float32::relu;
+    (void)float32::relu1;
+    (void)float32::relu6;
+    (void)float32::reshape;
+    (void)float32::resize_bilinear;
+    (void)float32::softmax;
+    (void)float32::tanh;
+
+    return table;
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/HexagonUtils.cpp b/1.0/HexagonUtils.cpp
new file mode 100644
index 0000000..bab6a5e
--- /dev/null
+++ b/1.0/HexagonUtils.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "HexagonUtils.h"
+#include <hidlmemory/mapping.h>
+#include <algorithm>
+#include <numeric>
+#include <vector>
+#include "OperationsUtils.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+bool isHexagonAvailable() {
+    int version = -1;
+    Controller::getInstance().version(&version);
+    if (version != 92) {
+        LOG(INFO) << "ATTEMPTING TO RESTART NNLIB";
+        Controller::getInstance().resetNnlib();
+        Controller::getInstance().version(&version);
+    }
+    return version == 92;
+}
+
+hexagon_nn_padding_type getPadding(uint32_t pad) {
+    switch (pad) {
+        case ::android::nn::kPaddingSame:
+            return NN_PAD_SAME;
+        case ::android::nn::kPaddingValid:
+            return NN_PAD_VALID;
+        case ::android::nn::kPaddingUnknown:
+        default:
+            return NN_PAD_NA;
+    };
+}
+
+hexagon_nn_padding_type getPadding(int32_t inWidth, int32_t inHeight, int32_t strideWidth,
+                                   int32_t strideHeight, int32_t filterWidth, int32_t filterHeight,
+                                   int32_t paddingLeft, int32_t paddingRight, int32_t paddingTop,
+                                   int32_t paddingBottom) {
+    return getPadding(::android::nn::getPaddingScheme(inWidth, inHeight, strideWidth, strideHeight,
+                                                      filterWidth, filterHeight, paddingLeft,
+                                                      paddingRight, paddingTop, paddingBottom));
+}
+
+op_type getFloatActivationFunction(FusedActivationFunc act) {
+    switch (act) {
+        case FusedActivationFunc::RELU:
+            return OP_Relu_f;
+        case FusedActivationFunc::RELU1:
+            return OP_Clamp_f;
+        case FusedActivationFunc::RELU6:
+            return OP_ReluX_f;
+        case FusedActivationFunc::NONE:
+            FALLTHROUGH_INTENDED;
+        default:
+            return OP_Nop;
+    };
+}
+
+op_type getQuantizedActivationFunction(FusedActivationFunc act) {
+    switch (act) {
+        case FusedActivationFunc::RELU:
+            return OP_QuantizedRelu_8;
+        case FusedActivationFunc::RELU1:
+            return OP_QuantizedClamp_8;
+        case FusedActivationFunc::RELU6:
+            return OP_QuantizedReluX_8;
+        case FusedActivationFunc::NONE:
+            FALLTHROUGH_INTENDED;
+        default:
+            return OP_Nop;
+    };
+}
+
+uint32_t getSize(OperandType type) {
+    static const uint32_t sizes[] = {
+        4,  // FLOAT32
+        4,  // INT32
+        4,  // UINT32
+        4,  // TENSOR_FLOAT32
+        4,  // TENSOR_INT32
+        1,  // TENSOR_SYMMETRICAL_QUANT8
+    };
+    HEXAGON_SOFT_ASSERT(static_cast<uint32_t>(type) < sizeof(sizes) / sizeof(*sizes),
+                        "Error: type exceeds max enum value");
+    return sizes[static_cast<uint32_t>(type)];
+}
+
+std::vector<uint32_t> getAlignedDimensions(const std::vector<uint32_t>& dims, uint32_t N) {
+    HEXAGON_SOFT_ASSERT_GE(
+        N, dims.size(),
+        "Error: constant data dimensions " << dims.size() << " exceeds alignment of " << N);
+    std::vector<uint32_t> dimensions(N - dims.size(), 1);
+    dimensions.insert(dimensions.end(), dims.begin(), dims.end());
+    return dimensions;
+}
+
+std::vector<RunTimePoolInfo> mapPools(const hidl_vec<hidl_memory>& pools) {
+    std::vector<RunTimePoolInfo> poolInfos;
+    poolInfos.reserve(pools.size());
+    bool fail = false;
+    for (const auto& pool : pools) {
+        poolInfos.emplace_back(pool, &fail);
+    }
+    HEXAGON_SOFT_ASSERT(!fail, "Error setting pools");
+    return poolInfos;
+}
+
+std::unordered_set<uint32_t> getPoolIndexes(const std::vector<RequestArgument>& inputsOutputs) {
+    std::unordered_set<uint32_t> indexes;
+    for (const RequestArgument& inputOutput : inputsOutputs) {
+        indexes.insert(inputOutput.location.poolIndex);
+    }
+    return indexes;
+}
+
+namespace {
+const uint8_t* getDataFromBlock(const hidl_vec<uint8_t>& block, uint32_t offset, uint32_t length) {
+    HEXAGON_SOFT_ASSERT_LE(offset + length, block.size(),
+                           "Error: trying to copy data from outside of block bounds");
+    return block.data() + offset;
+}
+
+const uint8_t* getDataFromPool(const RunTimePoolInfo& pool, uint32_t offset,
+                               [[maybe_unused]] uint32_t length) {
+    // HEXAGON_SOFT_ASSERT_LE(offset + length, pool->getSize(),
+    //                       "Error: trying to copy data from outside of pool bounds");
+    return pool.getBuffer() + offset;
+}
+}  // anonymous namespace
+
+const uint8_t* getData(const Operand& operand, const hidl_vec<uint8_t>& block,
+                       const std::vector<RunTimePoolInfo>& pools) {
+    switch (operand.lifetime) {
+        case OperandLifeTime::TEMPORARY_VARIABLE:
+            return nullptr;
+        case OperandLifeTime::MODEL_INPUT:
+        case OperandLifeTime::MODEL_OUTPUT:
+            HEXAGON_SOFT_ASSERT(false,
+                                "Error: trying to retrieve data that is only known at runtime");
+        case OperandLifeTime::CONSTANT_COPY:
+            return getDataFromBlock(block, operand.location.offset, operand.location.length);
+        case OperandLifeTime::CONSTANT_REFERENCE:
+            return getDataFromPool(pools[operand.location.poolIndex], operand.location.offset,
+                                   operand.location.length);
+        default:
+            HEXAGON_SOFT_ASSERT(false, "Error: unrecognized operand lifetime");
+    }
+}
+
+bool operator==(const hexagon_nn_input& lhs, const hexagon_nn_input& rhs) {
+    return lhs.src_id == rhs.src_id && lhs.output_idx == rhs.output_idx;
+}
+
+bool operator!=(const hexagon_nn_input& lhs, const hexagon_nn_input& rhs) {
+    return !(lhs == rhs);
+}
+
+bool operator==(const hexagon_nn_output& lhs, const hexagon_nn_output& rhs) {
+    return lhs.rank == rhs.rank && lhs.max_sizes[0] == rhs.max_sizes[0] &&
+           lhs.max_sizes[1] == rhs.max_sizes[1] && lhs.max_sizes[2] == rhs.max_sizes[2] &&
+           lhs.max_sizes[3] == rhs.max_sizes[3] && lhs.max_sizes[4] == rhs.max_sizes[4] &&
+           lhs.max_sizes[5] == rhs.max_sizes[5] && lhs.max_sizes[6] == rhs.max_sizes[6] &&
+           lhs.max_sizes[7] == rhs.max_sizes[7] && lhs.elementsize == rhs.elementsize &&
+           lhs.zero_offset == rhs.zero_offset && lhs.stepsize == rhs.stepsize;
+}
+
+bool operator!=(const hexagon_nn_output& lhs, const hexagon_nn_output& rhs) {
+    return !(lhs == rhs);
+}
+
+hexagon_nn_output make_hexagon_nn_output(const std::vector<uint32_t>& dims, uint32_t size) {
+    std::vector<uint32_t> alignedDims = getAlignedDimensions(dims, 4);
+    hexagon_nn_output output = {
+        .rank = std::min(8u, static_cast<uint32_t>(alignedDims.size())),
+        .max_sizes = {0, 0, 0, 0, 0, 0, 0, 0},
+        .elementsize = size,
+        .zero_offset = 0,
+        .stepsize = 0.0f,
+    };
+    for (size_t i = 0; i < alignedDims.size() && i < 8; ++i) {
+        output.max_sizes[i] = alignedDims[i];
+    }
+    return output;
+}
+
+// printers
+std::string toString(uint32_t val) {
+    return std::to_string(val);
+}
+
+std::string toString(float val) {
+    return std::to_string(val);
+}
+
+std::string toString(hexagon_nn_nn_id id) {
+    return std::to_string(static_cast<int32_t>(id));
+}
+
+std::string toString(op_type op) {
+    static const char* opText[] = {
+#define DEF_OP(NAME, ...) "OP_" #NAME,
+#include "hexagon_nn_controller/ops.def"
+#undef DEF_OP
+    };
+    return static_cast<size_t>(op) < sizeof(opText) / sizeof(char*)
+               ? opText[static_cast<size_t>(op)]
+               : "<invalid op_type>";
+}
+
+std::string toString(hexagon_nn_padding_type padding) {
+    static const char* paddingText[] = {
+        "NN_PAD_NA",
+        "NN_PAD_SAME",
+        "NN_PAD_VALID",
+        "NN_PAD_MIRROR_REFLECT",
+        "NN_PAD_MIRROR_SYMMETRIC",
+        "NN_PAD_SAME_CAFFE",
+    };
+    return static_cast<size_t>(padding) < sizeof(paddingText) / sizeof(char*)
+               ? paddingText[static_cast<size_t>(padding)]
+               : "<invalid hexagon_nn_padding_type>";
+}
+
+std::string toString(const hexagon_nn_input& input) {
+    return "hexagon_nn_input{.src_id: " + std::to_string(input.src_id) +
+           ", .output_idx: " + std::to_string(input.output_idx) + "}";
+}
+
+std::string toString(const hexagon_nn_output& output) {
+    return "hexagon_nn_output{.rank: " + std::to_string(output.rank) + ", .max_sizes: [" +
+           std::to_string(output.max_sizes[0]) + ", " + std::to_string(output.max_sizes[1]) + ", " +
+           std::to_string(output.max_sizes[2]) + ", " + std::to_string(output.max_sizes[3]) + ", " +
+           std::to_string(output.max_sizes[4]) + ", " + std::to_string(output.max_sizes[5]) + ", " +
+           std::to_string(output.max_sizes[6]) + ", " + std::to_string(output.max_sizes[7]) + "]" +
+           ", .elementsize: " + std::to_string(output.elementsize) +
+           ", .zero_offset: " + std::to_string(output.zero_offset) +
+           ", .stepsize: " + std::to_string(output.stepsize) + "}";
+}
+
+std::string toString(const hexagon_nn_tensordef& tensordef) {
+    return "hexagon_nn_tensordef{.batches: " + std::to_string(tensordef.batches) +
+           ", .height: " + std::to_string(tensordef.height) +
+           ", .width: " + std::to_string(tensordef.width) +
+           ", .depth: " + std::to_string(tensordef.depth) +
+           ", .data: " + std::to_string(reinterpret_cast<uintptr_t>(tensordef.data)) +
+           ", .dataLen: " + std::to_string(tensordef.dataLen) +
+           ", .data_valid_len: " + std::to_string(tensordef.data_valid_len) +
+           ", .unused: " + std::to_string(tensordef.unused) + "}";
+}
+
+std::string toString(const hexagon_nn_perfinfo& perfinfo) {
+    return "hexagon_nn_perfinfo{.node_id: " + std::to_string(perfinfo.node_id) +
+           ", .executions: " + std::to_string(perfinfo.executions) +
+           ", .counter_lo: " + std::to_string(perfinfo.counter_lo) +
+           ", .counter_hi: " + std::to_string(perfinfo.counter_hi) + "}";
+}
+
+std::string toString(const ::android::nn::Shape& shape) {
+    return "Shape{.type: " + toString(shape.type) +
+           ", .dimensions: " + toString(shape.dimensions.data(), shape.dimensions.size()) +
+           ", .scale: " + std::to_string(shape.scale) +
+           ", .zeroPoint: " + std::to_string(shape.offset) + "}";
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/HexagonUtils.h b/1.0/HexagonUtils.h
new file mode 100644
index 0000000..a50bb6f
--- /dev/null
+++ b/1.0/HexagonUtils.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_V1_0_UTILS_H
+#define ANDROID_HARDWARE_V1_0_UTILS_H
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.0/types.h>
+#include <string>
+#include <unordered_set>
+#include <vector>
+#include "CpuExecutor.h"
+#include "HexagonController.h"
+#include "OperationsUtils.h"
+#include "hexagon_nn_controller/hexagon_nn_controller.h"
+
+#define HEXAGON_SOFT_ASSERT(condition, message)                          \
+    if (!(condition)) {                                                  \
+        LOG(DEBUG) << __FILE__ << "::" << __LINE__ << " -- " << message; \
+        return {};                                                       \
+    }
+
+#define HEXAGON_SOFT_ASSERT_CMP(cmp, lhs, rhs, message)                        \
+    HEXAGON_SOFT_ASSERT(((lhs)cmp(rhs)), "failed " #lhs " " #cmp " " #rhs " (" \
+                                             << (lhs) << " " #cmp " " << (rhs) << "): " message)
+
+#define HEXAGON_SOFT_ASSERT_EQ(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(==, lhs, rhs, message)
+#define HEXAGON_SOFT_ASSERT_NE(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(!=, lhs, rhs, message)
+#define HEXAGON_SOFT_ASSERT_LT(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(<, lhs, rhs, message)
+#define HEXAGON_SOFT_ASSERT_LE(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(<=, lhs, rhs, message)
+#define HEXAGON_SOFT_ASSERT_GT(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(>, lhs, rhs, message)
+#define HEXAGON_SOFT_ASSERT_GE(lhs, rhs, message) HEXAGON_SOFT_ASSERT_CMP(>=, lhs, rhs, message)
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+namespace hexagon {
+
+using ::android::sp;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::neuralnetworks::V1_0::FusedActivationFunc;
+using ::android::hardware::neuralnetworks::V1_0::Operand;
+using ::android::nn::RunTimePoolInfo;
+
+bool isHexagonAvailable();
+
+hexagon_nn_padding_type getPadding(uint32_t pad);
+hexagon_nn_padding_type getPadding(int32_t inWidth, int32_t inHeight, int32_t strideWidth,
+                                   int32_t strideHeight, int32_t filterWidth, int32_t filterHeight,
+                                   int32_t paddingLeft, int32_t paddingRight, int32_t paddingTop,
+                                   int32_t paddingBottom);
+op_type getFloatActivationFunction(FusedActivationFunc act);
+op_type getQuantizedActivationFunction(FusedActivationFunc act);
+
+uint32_t getSize(OperandType type);
+std::vector<uint32_t> getAlignedDimensions(const std::vector<uint32_t>& dims, uint32_t N);
+
+std::vector<RunTimePoolInfo> mapPools(const hidl_vec<hidl_memory>& pools);
+
+std::unordered_set<uint32_t> getPoolIndexes(const std::vector<RequestArgument>& inputsOutputs);
+
+const uint8_t* getData(const Operand& operand, const hidl_vec<uint8_t>& block,
+                       const std::vector<RunTimePoolInfo>& pools);
+
+template <typename Type>
+std::vector<Type> transpose(uint32_t height, uint32_t width, const Type* input) {
+    std::vector<Type> output(height * width);
+    for (uint32_t i = 0; i < height; ++i) {
+        for (uint32_t j = 0; j < width; ++j) {
+            output[j * height + i] = input[i * width + j];
+        }
+    }
+    return output;
+}
+
+hexagon_nn_output make_hexagon_nn_output(const std::vector<uint32_t>& dims, uint32_t size);
+
+bool operator==(const hexagon_nn_input& lhs, const hexagon_nn_input& rhs);
+bool operator!=(const hexagon_nn_input& lhs, const hexagon_nn_input& rhs);
+bool operator==(const hexagon_nn_output& lhs, const hexagon_nn_output& rhs);
+bool operator!=(const hexagon_nn_output& lhs, const hexagon_nn_output& rhs);
+
+// printers
+std::string toString(uint32_t val);
+std::string toString(float val);
+std::string toString(hexagon_nn_nn_id id);
+std::string toString(op_type op);
+std::string toString(hexagon_nn_padding_type padding);
+std::string toString(const hexagon_nn_input& input);
+std::string toString(const hexagon_nn_output& output);
+std::string toString(const hexagon_nn_tensordef& tensordef);
+std::string toString(const hexagon_nn_perfinfo& perfinfo);
+std::string toString(const ::android::nn::Shape& input);
+
+template <typename Type>
+std::string toString(const Type* buffer, uint32_t count) {
+    std::string os = "[";
+    for (uint32_t i = 0; i < count; ++i) {
+        os += (i == 0 ? "" : ", ") + toString(buffer[i]);
+    }
+    return os += "]";
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              const hexagon_nn_input& obj) {
+    return os << toString(obj);
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              const hexagon_nn_output& obj) {
+    return os << toString(obj);
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              const hexagon_nn_tensordef& obj) {
+    return os << toString(obj);
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              const hexagon_nn_perfinfo& obj) {
+    return os << toString(obj);
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              const ::android::nn::Shape& obj) {
+    return os << toString(obj);
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
+                                              ErrorStatus status) {
+    return os << toString(status);
+}
+
+}  // namespace hexagon
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_V1_0_UTILS_H
diff --git a/1.0/PreparedModel.cpp b/1.0/PreparedModel.cpp
new file mode 100644
index 0000000..c95d56f
--- /dev/null
+++ b/1.0/PreparedModel.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-impl-hvx"
+
+#include "PreparedModel.h"
+#include <android-base/logging.h>
+#include <thread>
+#include "HexagonUtils.h"
+#include "ValidateHal.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+PreparedModel::PreparedModel(const Model& neuralNetworksModel,
+                             const std::shared_ptr<hexagon::Model>& hexagonModel)
+    : mNeuralNetworksModel(neuralNetworksModel), mHexagonModel(hexagonModel) {}
+
+PreparedModel::~PreparedModel() {}
+
+static void asyncExecute(const std::shared_ptr<hexagon::Model>& model, const Request& request,
+                         const sp<IExecutionCallback>& callback) {
+    ErrorStatus status =
+        model->execute(request) == true ? ErrorStatus::NONE : ErrorStatus::GENERAL_FAILURE;
+    Return<void> ret = callback->notify(status);
+    if (!ret.isOk()) {
+        LOG(ERROR) << "Error in callback's return type: " << ret.description();
+    }
+}
+
+Return<ErrorStatus> PreparedModel::execute(const Request& request,
+                                           const sp<IExecutionCallback>& callback) {
+    if (callback.get() == nullptr) {
+        LOG(ERROR) << "invalid callback passed to execute";
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+
+    if (!nn::validateRequest(request, mNeuralNetworksModel)) {
+        Return<void> ret = callback->notify(ErrorStatus::INVALID_ARGUMENT);
+        if (!ret.isOk()) {
+            LOG(ERROR) << "Error in callback's return type: " << ret.description();
+        }
+        return ErrorStatus::INVALID_ARGUMENT;
+    }
+    if (!hexagon::isHexagonAvailable()) {
+        Return<void> ret = callback->notify(ErrorStatus::DEVICE_UNAVAILABLE);
+        if (!ret.isOk()) {
+            LOG(ERROR) << "Error in callback's return type: " << ret.description();
+        }
+        return ErrorStatus::DEVICE_UNAVAILABLE;
+    }
+
+    // TODO: once nnlib hanging issue is resolved, make this function
+    // asynchronous again
+    asyncExecute(mHexagonModel, request, callback);
+
+    return ErrorStatus::NONE;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
diff --git a/1.0/PreparedModel.h b/1.0/PreparedModel.h
new file mode 100644
index 0000000..c2edd99
--- /dev/null
+++ b/1.0/PreparedModel.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 ANDROID_HARDWARE_NEURALNETWORKS_V1_0_PREPAREDMODEL_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_0_PREPAREDMODEL_H
+
+#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+#include <memory>
+#include "HexagonModel.h"
+#include "hexagon_nn_controller/hexagon_nn_controller.h"
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+
+struct PreparedModel : public IPreparedModel {
+   private:
+    PreparedModel() = delete;
+    PreparedModel(const PreparedModel&) = delete;
+    PreparedModel(PreparedModel&&) = delete;
+    PreparedModel& operator=(const PreparedModel&) = delete;
+    PreparedModel& operator=(PreparedModel&&) = delete;
+
+   public:
+    PreparedModel(const Model& neuralNetworksModel,
+                  const std::shared_ptr<hexagon::Model>& hexagonModel);
+    ~PreparedModel() override;
+
+    // Methods from IPreparedModel follow.
+    Return<ErrorStatus> execute(const Request& request,
+                                const sp<IExecutionCallback>& callback) override;
+
+   private:
+    Model mNeuralNetworksModel;
+    std::shared_ptr<hexagon::Model> mHexagonModel;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace neuralnetworks
+}  // namespace hardware
+}  // namespace android
+
+#endif  // ANDROID_HARDWARE_NEURALNETWORKS_V1_0_PREPAREDMODEL_H
diff --git a/1.0/Service.cpp b/1.0/Service.cpp
new file mode 100644
index 0000000..f1f74e3
--- /dev/null
+++ b/1.0/Service.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "android.hardware.neuralnetworks@1.0-service-hvx"
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.0/IDevice.h>
+#include <hidl/HidlTransportSupport.h>
+#include "Device.h"
+
+// Generated HIDL files
+using android::hardware::neuralnetworks::V1_0::IDevice;
+using android::hardware::neuralnetworks::V1_0::implementation::Device;
+
+int main() {
+    android::sp<IDevice> device = new Device();
+    android::hardware::configureRpcThreadpool(4, true /* will join */);
+    if (device->registerAsService("hvx") != android::OK) {
+        LOG(ERROR) << "Could not register service";
+        return 1;
+    }
+    android::hardware::joinRpcThreadpool();
+    LOG(ERROR) << "Hvx service exited!";
+    return 1;
+}
diff --git a/1.0/android.hardware.neuralnetworks@1.0-service-hvx.rc b/1.0/android.hardware.neuralnetworks@1.0-service-hvx.rc
new file mode 100644
index 0000000..538b869
--- /dev/null
+++ b/1.0/android.hardware.neuralnetworks@1.0-service-hvx.rc
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# 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.
+#
+
+service neuralnetworks_hal_service_hvx /vendor/bin/hw/android.hardware.neuralnetworks@1.0-service-hvx
+    class hal
+    user system
+    group system
diff --git a/1.0/hexagon_nn_controller/hexagon_nn_controller.h b/1.0/hexagon_nn_controller/hexagon_nn_controller.h
new file mode 100644
index 0000000..cc1aada
--- /dev/null
+++ b/1.0/hexagon_nn_controller/hexagon_nn_controller.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 HEXAGON_NN_CONTROLLER_H
+#define HEXAGON_NN_CONTROLLER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// includes
+#include "hexagon_nn_ops.h"
+
+// hexagon types
+
+typedef struct hexagon_nn_input {
+    unsigned int src_id;
+    unsigned int output_idx;
+} hexagon_nn_input;
+
+typedef struct hexagon_nn_output {
+    unsigned int rank;
+    unsigned int max_sizes[8];
+    unsigned int elementsize;
+    int zero_offset;
+    float stepsize;
+} hexagon_nn_output;
+
+typedef struct hexagon_nn_perfinfo {
+    unsigned int node_id;
+    unsigned int executions;
+    unsigned int counter_lo;
+    unsigned int counter_hi;
+} hexagon_nn_perfinfo;
+
+typedef int hexagon_nn_nn_id;
+
+typedef enum hexagon_nn_padding_type {
+    NN_PAD_NA,
+    NN_PAD_SAME,
+    NN_PAD_VALID,
+    NN_PAD_MIRROR_REFLECT,
+    NN_PAD_MIRROR_SYMMETRIC,
+    NN_PAD_SAME_CAFFE,
+    _32BIT_PLACEHOLDER_hexagon_nn_padding_type = 0x7fffffff
+} hexagon_nn_padding_type;
+
+typedef struct hexagon_nn_tensordef {
+    unsigned int batches;
+    unsigned int height;
+    unsigned int width;
+    unsigned int depth;
+    unsigned char* data;
+    int dataLen;
+    unsigned int data_valid_len;
+    unsigned int unused;
+} hexagon_nn_tensordef;
+
+// interface types
+
+typedef int (*hexagon_nn_controller_init_fn)(hexagon_nn_nn_id* g);
+
+typedef int (*hexagon_nn_controller_getlog_fn)(hexagon_nn_nn_id id, unsigned char* buf,
+                                               unsigned int length);
+
+typedef int (*hexagon_nn_controller_snpprint_fn)(hexagon_nn_nn_id id, unsigned char* buf,
+                                                 unsigned int length);
+
+typedef int (*hexagon_nn_controller_set_debug_level_fn)(hexagon_nn_nn_id id, int level);
+
+typedef int (*hexagon_nn_controller_prepare_fn)(hexagon_nn_nn_id id);
+
+typedef int (*hexagon_nn_controller_append_node_fn)(
+    hexagon_nn_nn_id id, unsigned int node_id, op_type operation, hexagon_nn_padding_type padding,
+    const hexagon_nn_input* inputs, unsigned int num_inputs, const hexagon_nn_output* outputs,
+    unsigned int num_outputs);
+
+typedef int (*hexagon_nn_controller_append_const_node_fn)(hexagon_nn_nn_id id, unsigned int node_id,
+                                                          unsigned int batches, unsigned int height,
+                                                          unsigned int width, unsigned int depth,
+                                                          const unsigned char* data,
+                                                          unsigned int data_len);
+
+typedef int (*hexagon_nn_controller_execute_new_fn)(hexagon_nn_nn_id id,
+                                                    const hexagon_nn_tensordef* inputs,
+                                                    unsigned int n_inputs,
+                                                    hexagon_nn_tensordef* outputs,
+                                                    unsigned int n_outputs);
+
+typedef int (*hexagon_nn_controller_execute_fn)(hexagon_nn_nn_id id, unsigned int batches_in,
+                                                unsigned int height_in, unsigned int width_in,
+                                                unsigned int depth_in, const unsigned char* data_in,
+                                                unsigned int data_len_in, unsigned int* batches_out,
+                                                unsigned int* height_out, unsigned int* width_out,
+                                                unsigned int* depth_out, unsigned char* data_out,
+                                                unsigned int data_out_max,
+                                                unsigned int* data_out_size);
+
+typedef int (*hexagon_nn_controller_teardown_fn)(hexagon_nn_nn_id id);
+
+typedef int (*hexagon_nn_controller_get_perfinfo_fn)(hexagon_nn_nn_id id,
+                                                     hexagon_nn_perfinfo* info_out,
+                                                     unsigned int info_out_len,
+                                                     unsigned int* n_items_out);
+
+typedef int (*hexagon_nn_controller_reset_perfinfo_fn)(hexagon_nn_nn_id id, unsigned int event);
+
+typedef int (*hexagon_nn_controller_version_fn)(int* ver);
+
+typedef int (*hexagon_nn_controller_last_execution_cycles_fn)(hexagon_nn_nn_id id,
+                                                              unsigned int* cycles_lo,
+                                                              unsigned int* cycles_hi);
+
+typedef int (*hexagon_nn_controller_GetHexagonBinaryVersion_fn)(int* ver);
+
+typedef int (*hexagon_nn_controller_PrintLog_fn)(const unsigned char* data_in,
+                                                 unsigned int data_in_len);
+
+typedef int (*hexagon_nn_controller_op_name_to_id_fn)(const char* name, unsigned int* id);
+
+typedef int (*hexagon_nn_controller_op_id_to_name_fn)(const unsigned int id, char* name,
+                                                      int name_len);
+
+typedef int (*hexagon_nn_controller_disable_dcvs_fn)();
+
+typedef int (*hexagon_nn_controller_set_powersave_level_fn)(unsigned int level);
+
+typedef int (*hexagon_nn_controller_config_fn)();
+
+typedef unsigned int (*hexagon_nn_controller_get_dsp_offset_fn)();
+
+typedef int (*hexagon_nn_controller_boost_fn)(int bus_usage);
+
+typedef int (*hexagon_nn_controller_slow_fn)();
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // HEXAGON_NN_CONTROLLER_H
diff --git a/1.0/hexagon_nn_controller/hexagon_nn_ops.h b/1.0/hexagon_nn_controller/hexagon_nn_ops.h
new file mode 100644
index 0000000..282095b
--- /dev/null
+++ b/1.0/hexagon_nn_controller/hexagon_nn_ops.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *    * Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *
+ *    * Neither the name of The Linux Foundation nor the names of its
+ *      contributors may be used to endorse or promote products derived
+ *      from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+#ifndef HEXAGON_NN_GRAPH_OPS_H
+#define HEXAGON_NN_GRAPH_OPS_H 1
+/*
+ * 
+ * Now that that's out of the way, let's get to the good stuff.
+ * 
+ * This defines common types used.
+ */
+
+/**
+ * This file comes from:
+ * https://source.codeaurora.org/quic/hexagon_nn/nnlib/tree/interface/hexagon_nn_ops.h
+ */
+
+#define DEF_OP(NAME,...) OP_##NAME,
+typedef enum op_type_enum {
+#include "ops.def"
+	NN_OPS_MAX
+} op_type;
+#undef DEF_OP
+
+#endif
+
diff --git a/1.0/hexagon_nn_controller/ops.def b/1.0/hexagon_nn_controller/ops.def
new file mode 100644
index 0000000..28e0b8f
--- /dev/null
+++ b/1.0/hexagon_nn_controller/ops.def
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the
+ * disclaimer below) provided that the following conditions are met:
+ *
+ *    * Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *    * Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *
+ *    * Neither the name of The Linux Foundation nor the names of its
+ *      contributors may be used to endorse or promote products derived
+ *      from this software without specific prior written permission.
+ * '
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
+ * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
+ * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/* 
+ * You probably want to 
+ * 
+ *    ##    #####   #####
+ *   #  #   #    #  #    #
+ *  #    #  #    #  #    #
+ *  ######  #    #  #    #
+ *  #    #  #    #  #    #
+ *  #    #  #####   #####
+ * 
+ * 
+ *  #    #   ####   #####   ######   ####
+ *  ##   #  #    #  #    #  #       #
+ *  # #  #  #    #  #    #  #####    ####
+ *  #  # #  #    #  #    #  #            #
+ *  #   ##  #    #  #    #  #       #    #
+ *  #    #   ####   #####   ######   ####
+ * 
+ * 
+ *    ##     #####
+ *   #  #      #
+ *  #    #     #
+ *  ######     #
+ *  #    #     #
+ *  #    #     #
+ * 
+ * 
+ *   #####  #    #  ######
+ *     #    #    #  #
+ *     #    ######  #####
+ *     #    #    #  #
+ *     #    #    #  #
+ *     #    #    #  ######
+ * 
+ * 
+ *  ######  #    #  #####
+ *  #       ##   #  #    #
+ *  #####   # #  #  #    #
+ *  #       #  # #  #    #
+ *  #       #   ##  #    #
+ *  ######  #    #  #####
+ * 
+ * otherwise the interface becomes incompatible.
+ */
+
+/**
+ * This file comes from:
+ * https://source.codeaurora.org/quic/hexagon_nn/nnlib/tree/interface/ops.def
+ */
+
+DEF_OP(INPUT)
+DEF_OP(OUTPUT)
+DEF_OP(Nop)
+DEF_OP(Const)
+DEF_OP(Check)
+DEF_OP(Close_f)
+DEF_OP(Close_quint8)
+DEF_OP(Close_q_quint8)
+DEF_OP(Close_int32)
+DEF_OP(Close_qint32)
+DEF_OP(PPrint_8)
+DEF_OP(PPrint_32)
+DEF_OP(PPrint_f)
+DEF_OP(PreFree)
+DEF_OP(Flatten)
+
+#ifndef DEF_OP_WREF
+#define DEF_OP_WREF(NAME) DEF_OP(NAME) DEF_OP(NAME##_ref)
+#define __SELF_DEF_OP_WREF
+#endif
+
+DEF_OP_WREF(QuantizedConv2d_8x8to32)
+DEF_OP_WREF(QuantizedMatMul_8x8to32)
+DEF_OP_WREF(QuantizeDownAndShrinkRange_32to8)
+DEF_OP_WREF(QuantizedRelu_8)
+DEF_OP_WREF(QuantizedReluX_8)
+DEF_OP_WREF(QuantizedMaxPool_8)
+DEF_OP_WREF(QuantizedAvgPool_8)
+DEF_OP_WREF(QuantizedL2Pool_8)
+DEF_OP_WREF(QuantizedConcat_8)
+DEF_OP_WREF(QuantizedBiasAdd_8p8to32)
+DEF_OP_WREF(Min_f)
+DEF_OP_WREF(Max_f)
+DEF_OP_WREF(Quantize)
+DEF_OP_WREF(Dequantize)
+DEF_OP_WREF(Supernode_8x8p8to8)
+
+DEF_OP(QuantizedFlatten)
+DEF_OP(Softmax_f)
+DEF_OP(Conv2d_f)
+DEF_OP(MatMul_f)
+DEF_OP(Relu_f)
+DEF_OP(ReluX_f)
+DEF_OP(AvgPool_f)
+DEF_OP(L2Pool_f)
+DEF_OP(MaxPool_f)
+DEF_OP(Concat_f)
+DEF_OP(BiasAdd_f)
+DEF_OP(LRN_f)
+
+DEF_OP(Variable)
+DEF_OP(Assign)
+DEF_OP(Reshape)
+DEF_OP(QuantizedReshape)
+DEF_OP(Tanh_f)
+DEF_OP(Sigmoid_f)
+DEF_OP(Slice_8)
+DEF_OP(Slice_f)
+DEF_OP(QuantizedSlice_8)
+DEF_OP(Add_f)
+DEF_OP(Mul_f)
+DEF_OP(Minimum_f)
+DEF_OP(Maximum_f)
+
+DEF_OP_WREF(Requantize_32to8)
+DEF_OP_WREF(RequantizationRange_32)
+
+DEF_OP(Neg_f)
+DEF_OP(Sub_f)
+DEF_OP(AddN_f)
+DEF_OP(Range_int32)
+DEF_OP(Rank_int32)
+DEF_OP(Transpose_int32)
+DEF_OP(Transpose_f)
+DEF_OP(InstanceNorm_f)
+DEF_OP_WREF(QuantizedInstanceNorm_8)
+DEF_OP(Sub_int32)
+DEF_OP(Add_int32)
+DEF_OP(Split_f)
+DEF_OP(Dequantize_qint32_f)
+DEF_OP(PRelu_f)
+DEF_OP_WREF(QuantizedPRelu_8)
+DEF_OP(Sum_f)
+DEF_OP(Prod_f)
+DEF_OP(Mul_int32)
+DEF_OP(LogicalAnd_int32)
+DEF_OP(LogicalOr_int32)
+DEF_OP(LogicalXor_int32)
+DEF_OP(Shape_int32)
+DEF_OP(Pack_int32)
+DEF_OP(MirrorPad_f)
+DEF_OP(ResizeNearestNeighbor_f)
+DEF_OP(StridedSlice_int32)
+DEF_OP(StridedSlice_f)
+DEF_OP(ExpandDims_int32)
+DEF_OP(ExpandDims_f)
+
+DEF_OP(LogSoftmax_f)
+DEF_OP(Split_int32)
+DEF_OP(QuantizedSplit_8)
+
+DEF_OP(Deconv_f)
+DEF_OP_WREF(QuantizedDeconv_8x8to32)
+
+DEF_OP_WREF(QuantizedMul_8x8to32)
+DEF_OP_WREF(QuantizedAdd_8p8to32)
+DEF_OP_WREF(QuantizedSigmoid_8)
+DEF_OP_WREF(QuantizedTanh_8)
+DEF_OP_WREF(QuantizedSoftmax_8)
+DEF_OP_WREF(QuantizedLRN_8)
+DEF_OP_WREF(Quantizedpad2d_frame_8p)
+DEF_OP_WREF(QuantizedSub_8p8to32)
+DEF_OP_WREF(QuantizedMaximum_8)
+DEF_OP_WREF(QuantizedMinimum_8)
+
+DEF_OP(Pad_f)
+DEF_OP(SpaceToBatchND_f)
+DEF_OP(BatchToSpaceND_f)
+DEF_OP(QuantizedPad_8)
+DEF_OP(ResizeBilinear_f)
+DEF_OP(ConcatV2_f)
+DEF_OP(ConcatV2_int32)
+DEF_OP(Prod_int32)
+DEF_OP(Slice_int32)
+
+DEF_OP(QuantizedAdd_8p8to8)
+DEF_OP(QuantizedResizeBilinear_8)
+DEF_OP(Supernode_8x8p8to8_d32)
+DEF_OP(Convert_to_d32)
+DEF_OP(Convert_from_d32)
+DEF_OP_WREF(QuantizedMaxPool_8_d32)
+DEF_OP_WREF(QuantizedConcat_8_d32)
+DEF_OP_WREF(QuantizedAvgPool_8_d32)
+
+DEF_OP(Sink)
+
+DEF_OP_WREF(QuantizedPRelu_8_d32)
+DEF_OP_WREF(AutoQuantize)
+DEF_OP_WREF(QuantizedDepthwiseConv2d_8x8to32)
+DEF_OP(DepthwiseConv2d_f)
+DEF_OP(DepthwiseSupernode_8x8p8to8)
+DEF_OP(DepthwiseSupernode_8x8p8to8_d32)
+
+DEF_OP_WREF(QuantizedMul_8x8to8_d32)
+
+DEF_OP(FullyConnected_u8)
+#if 0
+DEF_OP_WREF(QuantizedFC_8x8p8to8)
+#endif
+
+DEF_OP_WREF(QuantizedAdd_8p8to8_d32)
+
+DEF_OP_WREF(QuantizedClamp_8)
+DEF_OP(Clamp_f)
+DEF_OP(QuantizeForTest_d32)
+DEF_OP(Close_d32)
+DEF_OP_WREF(QuantizedSub_8p8to8_d32)
+
+DEF_OP(InputSupernode_8x8p8to8_outd32)
+DEF_OP(QuantizedLRN_8_d32)
+DEF_OP_WREF(QuantizedBiasAdd_32p32to32)
+DEF_OP_WREF(Quantize_int32)
+
+DEF_OP(Supernode_8x8p32to8)
+DEF_OP(DepthwiseSupernode_8x8p32to8)
+DEF_OP(Supernode_8x8p32to8_d32)
+DEF_OP(DepthwiseSupernode_8x8p32to8_d32)
+DEF_OP(InputSupernode_8x8p32to8_outd32)
+
+DEF_OP(PPrint_8_d32)
+DEF_OP(PPrintWithPadding_8_d32)
+DEF_OP_WREF(AutoQuantize_d32)
+
+DEF_OP_WREF(QuantizedTanh_8_d32)
+DEF_OP_WREF(QuantizedSigmoid_8_d32)
+DEF_OP_WREF(QuantizedSoftmax_8_d32)
+
+
+DEF_OP_WREF(QuantizedL2Pool_8_d32)
+
+DEF_OP(Gather_f)
+DEF_OP(Gather_int32)
+DEF_OP(Gather_8)
+DEF_OP(Table_f)
+DEF_OP(Table_int32)
+DEF_OP(Table_8)
+
+DEF_OP(FillPadding_8_d32)
+DEF_OP(QuantizedResizeBilinear_8_d32)
+#ifdef __SELF_DEF_OP_WREF
+#undef __SELF_DEF_OP_WREF
+#undef DEF_OP_WREF
+#endif
+
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..eba2a93
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+subdirs=["neuralnetworks/hvxservice/1.0"]
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..14b5e8b
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,21 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# 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.
+#
+
+[Options]
+ignore_merged_commits = true
+
+[Builtin Hooks]
+clang_format = true