--- /dev/null
+add_library(nnop INTERFACE)
+target_include_directories(nnop INTERFACE include)
+target_link_libraries(nnop INTERFACE nncc_core)
+
+nncc_find_package(GTest QUIET)
+
+if(NOT GTest_FOUND)
+ return()
+endif(NOT GTest_FOUND)
+
+file(GLOB_RECURSE TESTS "src/*.test.cpp")
+
+add_executable(nnop_test ${TESTS})
+target_link_libraries(nnop_test nnop)
+target_link_libraries(nnop_test gtest_main)
+add_test(nnop_test nnop_test)
--- /dev/null
+#ifndef __NNOP_CONV2D_H__
+#define __NNOP_CONV2D_H__
+
+#include <nncc/core/ADT/feature/Shape.h>
+#include <nncc/core/ADT/feature/Reader.h>
+#include <nncc/core/ADT/feature/Accessor.h>
+
+#include <nncc/core/ADT/kernel/Shape.h>
+#include <nncc/core/ADT/kernel/Reader.h>
+
+#include <nncc/core/ADT/PadInfo.h>
+#include <nncc/core/ADT/StrideInfo.h>
+
+namespace nnop
+{
+
+template<typename OutputDType, typename InputDType, typename KernelDType>
+void conv(const nncc::core::ADT::feature::Shape &out_shape,
+ nncc::core::ADT::feature::Accessor<OutputDType> &out_data,
+ const nncc::core::ADT::feature::Shape &in_shape,
+ const nncc::core::ADT::feature::Reader<InputDType> &in_data,
+ const nncc::core::ADT::kernel::Shape &ker_shape,
+ const nncc::core::ADT::kernel::Reader<KernelDType> &ker_data,
+ const nncc::core::ADT::PadInfo &pad_info, const nncc::core::ADT::StrideInfo &stride_info)
+{
+ for (uint32_t out_ch = 0; out_ch < out_shape.depth(); ++out_ch)
+ {
+ for (uint32_t out_row = 0; out_row < out_shape.height(); ++out_row)
+ {
+ for (uint32_t out_col = 0; out_col < out_shape.width(); ++out_col)
+ {
+ OutputDType out_value = 0;
+
+ for (uint32_t ker_ch = 0; ker_ch < ker_shape.depth(); ++ker_ch)
+ {
+ for (uint32_t ker_row = 0; ker_row < ker_shape.height(); ++ker_row)
+ {
+ for (uint32_t ker_col = 0; ker_col < ker_shape.width(); ++ker_col)
+ {
+ const uint32_t in_ch = ker_ch;
+ const int64_t in_row = stride_info.vertical() * out_row - pad_info.top() + ker_row;
+ const int64_t in_col = stride_info.horizontal() * out_col - pad_info.left() + ker_col;
+
+ const bool is_padding = (in_row < 0) || (in_row >= in_shape.height())
+ || (in_col < 0) || (in_col >= in_shape.width());
+
+ const auto in_value = (is_padding)
+ ? 0
+ : in_data.at(in_ch, static_cast<uint32_t>(in_row), static_cast<uint32_t>(in_col));
+
+ const auto ker_value = ker_data.at(out_ch, in_ch, ker_row, ker_col);
+
+ out_value += in_value * ker_value;
+ }
+ }
+ }
+
+ out_data.at(out_ch, out_row, out_col) = out_value;
+ }
+ }
+ }
+}
+
+} // namespace nnop
+
+#endif // __NNOP_CONV2D_H__
--- /dev/null
+#include "nnop/Conv2D.h"
+
+#include <nncc/core/ADT/feature/Overlay.h>
+#include <nncc/core/ADT/feature/CHWLayout.h>
+
+#include <nncc/core/ADT/kernel/Overlay.h>
+#include <nncc/core/ADT/kernel/NCHWLayout.h>
+
+#include <gtest/gtest.h>
+
+using namespace nncc::core::ADT;
+
+TEST(CONV2D, conv_1)
+{
+ const feature::Shape ofm_shape{1, 1, 1};
+ int ofm_data[] = { 0 };
+ auto ofm_overlay = feature::make_overlay<int, feature::CHWLayout>(ofm_shape, ofm_data);
+
+ const feature::Shape ifm_shape{1, 3, 3};
+ int ifm_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+ auto ifm_overlay = feature::make_overlay<int, feature::CHWLayout>(ifm_shape, ifm_data);
+
+ const kernel::Shape ker_shape{1, 1, 3, 3};
+ int ker_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
+ auto ker_overlay = kernel::make_overlay<int, kernel::NCHWLayout>(ker_shape, ker_data);
+
+ const PadInfo pad{0, 0, 0, 0};
+ const StrideInfo stride{1, 1};
+
+ nnop::conv(ofm_shape, ofm_overlay, ifm_shape, ifm_overlay, ker_shape, ker_overlay, pad, stride);
+
+ EXPECT_EQ(ofm_data[0], 204);
+}