+++ /dev/null
-/**
- * Proprietary
- * Copyright (C) 2019 Samsung Electronics
- * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
- */
-/**
- * @file ne_core_mem_test.cpp
- * @date 30 Jun 2019
- * @brief UnitTests to test functions in memory allocator for NPU Engine
- * @author Dongju Chae <dongju.chae@samsung.com>
- * @bug No known bugs except for NYI items
- */
-
-#include <unistd.h>
-#include <sys/mman.h>
-#include <gtest/gtest.h>
-#include <poll.h>
-
-/** below are internal C-headers */
-extern "C"
-{
- #include <ne-mem.h>
- #include <ne-conf.h>
-}
-
-#define K (1 << 10)
-#define M (1 << 20)
-
-/**
- * The below tests N7/8's behaviors in Activity Sequences
- * (suprem.sec.samsung.net/confluence/display/ODLC/ActSeq+Host-Input+Mode,+Normal+and+Easy)
- */
-
-/**
- * @brief register NPU model using internal memory pool
- * @detail The memory size of model and I/O buffer will be provided as arguments
- */
-TEST (ne_core_mem_test, register_model_internal)
-{
- mem *mem = GET_MEM ();
- hwmem *hwmem;
- uint64_t slice_size;
- uint64_t pool_size;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- mem->fini();
- mem->init(10 * M, &pool_size); /* use INTERNAL */
-
- slice_size = mem->get_pool_size() / 10;
-
- /* check available memory size and allocate memory for the model */
- ASSERT_NE (mem->alloc (slice_size * 11, &hwmem), 0);
- ASSERT_EQ (mem->alloc (slice_size * 5, &hwmem), 0);
- ASSERT_EQ (hwmem->size, slice_size * 5);
- ASSERT_EQ (mem->get_used_size(), slice_size * 5);
-
- /* check hwmem resize */
- ASSERT_NE (mem->realloc (hwmem, slice_size * 11), 0);
- ASSERT_EQ (mem->realloc (hwmem, slice_size * 7), 0);
- ASSERT_EQ (hwmem->size, slice_size * 7);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7);
-
-#ifdef ENABLE_BUFFERING
- /* config I/O buffer size (>= 3x size for triple buffering) */
- ASSERT_EQ (mem->resize_buffers (slice_size), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7 + slice_size * 3);
- ASSERT_EQ (mem->resize_buffers (slice_size / 2), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7 + (slice_size / 2) * 3);
- ASSERT_NE (mem->resize_buffers (slice_size * 2), 0);
- ASSERT_EQ (mem->resize_buffers (0), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7);
-#endif
-
- mem->dealloc (hwmem);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- mem->fini();
-
- /* rollback */
- mem->init (conf->reserved_mem_size, &pool_size);
-}
-
-/**
- * @brief register NPU model using kernel cma allocator
- * @detail The memory size of model and I/O buffer will be provided as arguments
- */
-TEST (ne_core_mem_test, register_model_kernel_cma)
-{
- mem *mem = GET_MEM ();
- hwmem *hwmem;
- uint64_t slice_size;
- uint64_t pool_size;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- mem->fini();
- mem->init(0, &pool_size); /* use KERNEL_CMA */
-
- ASSERT_EQ (mem->get_pool_size(), 0);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- slice_size = 1 * M;
-
- /* check available memory size and allocate memory for the model */
- ASSERT_EQ (mem->alloc (slice_size * 5, &hwmem), 0);
- ASSERT_EQ (hwmem->size, slice_size * 5);
- ASSERT_EQ (mem->get_used_size(), slice_size * 5);
-
- /* check hwmem resize */
- ASSERT_EQ (mem->realloc (hwmem, slice_size * 7), 0);
- ASSERT_EQ (hwmem->size, slice_size * 7);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7);
-
-#ifdef ENABLE_BUFFERING
- /* config I/O buffer size (>= 3x size for triple buffering) */
- ASSERT_EQ (mem->resize_buffers (slice_size), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7 + slice_size * 3);
- ASSERT_EQ (mem->resize_buffers (slice_size / 2), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7 + (slice_size / 2) * 3);
- ASSERT_EQ (mem->resize_buffers (0), 0);
- ASSERT_EQ (mem->get_used_size(), slice_size * 7);
-#endif
-
- mem->dealloc (hwmem);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- mem->fini();
-
- /* rollback */
- mem->init (conf->reserved_mem_size, &pool_size);
-}
-
-#ifdef ENABLE_BUFFERING
-/**
- * @brief prepare input buffer
- */
-TEST (ne_core_mem_test, prepare_input)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- hwmem *hwmem;
- uint64_t buf_size = 1 * K;
- uint64_t *ptr, i;
- int err;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- ASSERT_EQ (mem->resize_buffers (buf_size), 0);
-
- /* get an input buffer & write contents into there */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-
- ASSERT_EQ (buffer_get_hwmem (buffer, &hwmem), 0);
- ASSERT_EQ (hwmem_get_data (hwmem, (void **) &ptr), 0);
-
- for (i = 0; i < buffer->size; i += sizeof (uint64_t)) {
- ptr[i / sizeof (uint64_t)] = i;
- }
-
- mem->return_buffer (buffer);
-
- /* In this time, this buffer is ready for N4 and N4x */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
-
- /* get another buffer for the next frame */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
-
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-}
-
-/**
- * @brief when output is ready (in a syncronized manner)
- */
-TEST (ne_core_mem_test, output_ready)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- uint64_t buf_size = 1 * K;
- int err;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
-
- ASSERT_EQ (mem->resize_buffers (buf_size), 0);
-
- /* frame 1 */
- /* N1) feed input data into buffer */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-
- /* assume that input is being written */
-
- /* ... */
-
- /* it's now validated */
- mem->return_buffer (buffer);
-
- /* N4) input data is ready; and NPU can perform the processing */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_NPU_RUNNING);
-
- /* the host will call runNPUasync() */
-
- /* ... */
-
- /**
- * Then, let's assume the event from the kernel is delivered
- * if post processing is required, validate the buffer after post processing
- */
-
- /* ... */
-
- mem->return_buffer (buffer);
-
- /* N1) it's time to return output data to the host */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_OUTPUT_RETURN);
-
- /* after transmitting the output to HOST, it's no longer used */
-
- /* ... */
-
- mem->return_buffer (buffer);
-
- /* frame 2; same behaviors */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- ASSERT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-
- /* ... */
- mem->return_buffer (buffer);
-}
-
-/**
- * @brief input thread with input buffer
- */
-static void *
-input_thread (void* data)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- int i, num_frames = *(int *) data;
- int err;
-
- for (i = 0; i < num_frames; i++) {
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- if (buffer != NULL) {
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-
- /* Input data will be written in the buffer */
-
- /* ... */
-
- mem->return_buffer (buffer);
- }
- }
-
- return NULL;
-}
-
-/**
- * @brief npu thread with npu buffer
- */
-static void *
-npu_thread (void* data)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- int i, num_frames = *(int *) data;
- int err;
-
- for (i = 0; i < num_frames; i++) {
-
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- if (buffer != NULL) {
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_NPU_RUNNING);
-
- /* NPU will read/write data in the buffer */
-
- /* ... */
-
- mem->return_buffer (buffer);
- }
- }
-
- return NULL;
-}
-
-/**
- * @brief output thread with output buffer
- */
-static void *
-output_thread (void* data)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- int i, num_frames = *(int *) data;
- int err;
-
- for (i = 0; i < num_frames; i++) {
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- if (buffer != NULL) {
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_OUTPUT_RETURN);
-
- /* Output will be delivered to the host */
-
- /* ... */
-
- mem->return_buffer (buffer);
- }
- }
-
- return NULL;
-}
-
-/**
- * @brief running in parallel with fork()
- */
-TEST (ne_core_mem_test, running_parallel)
-{
- mem *mem = GET_MEM ();
- uint64_t buf_size = 1 * K;
- int status, num_frames = 10;
- pthread_t thread[3];
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- ASSERT_EQ (mem->resize_buffers (buf_size), 0);
-
- pthread_create (&thread[0], NULL, input_thread, (void*) (&num_frames));
- pthread_create (&thread[1], NULL, npu_thread, (void*) (&num_frames));
- pthread_create (&thread[2], NULL, output_thread, (void*) (&num_frames));
-
- pthread_join (thread[0], (void **)&status);
- pthread_join (thread[1], (void **)&status);
- pthread_join (thread[2], (void **)&status);
-}
-#endif
-
-/**
- * @brief test for memory compaction
- */
-TEST (ne_core_mem_test, memory_compaction)
-{
- mem *mem = GET_MEM ();
- hwmem *hwmem[6];
- uint64_t slice_size, offset;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
-
- /* it's only for INTERNAL compaction */
- if (mem->get_pool_size() == 0)
- return;
-
- slice_size = mem->get_pool_size() / 10;
-
- /**
- * compaction scenario 1 (each star denotes 100K memory block)
- * check whether compaction can move or shift it to free slots
- * BOTTOM TOP
- * original : [*|**|*|***|**|*]
- * after dealloc : [ |**| |***| |*]
- * after compact : [*|**|***| ]
- */
-
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[0]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 2, &hwmem[1]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[2]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 3, &hwmem[3]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 2, &hwmem[4]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[5]), 0); /* full */
-
- /* make some fragmentations */
- mem->dealloc (hwmem[0]);
- mem->dealloc (hwmem[2]);
- mem->dealloc (hwmem[4]);
-
- /* do compaction & allocate again */
- ASSERT_EQ (mem->alloc (slice_size * 4, &hwmem[0]), 0);
- ASSERT_EQ (hwmem_get_offset (hwmem[0], &offset), 0);
- ASSERT_EQ (offset, slice_size * 6);
-
- mem->dealloc (hwmem[0]);
- mem->dealloc (hwmem[1]);
- mem->dealloc (hwmem[3]);
- mem->dealloc (hwmem[5]);
-
- /**
- * compaction scenario 2 (each star denotes 100K memory block)
- * check it's merged with left/right chunks
- * BOTTOM TOP
- * original : [*|**|***|*|*|**]
- * after dealloc : [ |**| |*| ]
- * after compact : [*|**| ]
- */
-
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[0]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 2, &hwmem[1]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 3, &hwmem[2]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[3]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 1, &hwmem[4]), 0);
- ASSERT_EQ (mem->alloc (slice_size * 2, &hwmem[5]), 0); /* full */
-
- /* make some fragmentations */
- mem->dealloc (hwmem[0]);
- mem->dealloc (hwmem[2]);
- mem->dealloc (hwmem[4]);
- mem->dealloc (hwmem[5]);
-
- /* do compaction & allocate again */
- ASSERT_EQ (mem->alloc (slice_size * 7, &hwmem[0]), 0 );
- ASSERT_EQ (hwmem_get_offset (hwmem[0], &offset), 0);
- ASSERT_EQ (offset, slice_size * 3);
-
- mem->dealloc (hwmem[0]);
- mem->dealloc (hwmem[1]);
- mem->dealloc (hwmem[3]);
-}
-
-#ifdef ENABLE_BUFFERING
-/**
- * @brief data struct to pass mode and sync primitive to helper thread */
-typedef struct {
- pthread_mutex_t * mutex; /**< synchronization primitive */
- npu_async_mode mode; /**< async mode for this thread to use */
-} thread_modes;
-
-/**
- * @brief helper thread for waiting for input processing
- */
-static void *
-get_buffer_wait_thread (void *data)
-{
- mem *mem = GET_MEM ();
- buffer *buffer;
- int err;
- thread_modes *mode;
-
- mode = (thread_modes *) data;
- pthread_mutex_lock (mode->mutex);
-
- buffer = mem->get_next_buffer (mode->mode, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_INPUT_WRITING);
-
- /** cleanup */
-
- /* frame 1, return to INPUT_READY */
- mem->return_buffer (buffer);
- /* frame 1, return to NPU_RUNNING */
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_NPU_RUNNING);
- /* frame 1, return to OUPUT_READY*/
- mem->return_buffer (buffer);
- buffer = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer), BUFFER_STATE_OUTPUT_RETURN);
- /* frame 1, return to EMPTY */
- mem->return_buffer (buffer);
-
- pthread_mutex_unlock (mode->mutex);
- return NULL;
-}
-
-/**
- * @brief test handling input priorities and synchronization for memory
- */
-TEST (ne_core_mem_test, get_next_buffer_modes)
-{
- mem *mem = GET_MEM ();
- buffer *buffer1, *buffer2;
- uint64_t buf_size = 1 * K;
- int err;
- pthread_t thread;
- pthread_mutex_t mutex1;
- thread_modes th_mode;
-
- mem->cleanup();
-
- ASSERT_EQ (mem->get_pool_size(), conf->reserved_mem_size);
- ASSERT_EQ (mem->get_used_size(), 0);
-
- ASSERT_EQ (pthread_mutex_init (&mutex1, NULL), 0);
-
- ASSERT_EQ (mem->resize_buffers (buf_size), 0);
-
- /**
- * After return_buffer(), we cannot ensure the buffer state because it no longer
- * has the ownership of returned buffers. So, state checking after return_buffer()
- * may provide unexpected values if there are co-running threads. Thus, we should
- * check buffer states only after get_next_buffer().
- */
-
- /** WAIT */
-
- /* frame 1 */
- /* get input buffer, basic case */
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_INPUT_WRITING);
-
- /* frame 2 */
- /**
- * get input buffer, which is not available, so this will block till its
- * available
- */
- th_mode.mutex = &mutex1;
- th_mode.mode = NPUASYNC_WAIT;
- pthread_create (&thread, NULL, get_buffer_wait_thread, (void *) &th_mode);
-
- /** wait for a few seconds ensuring that other thread is still waiting */
- sleep (2);
- EXPECT_NE(pthread_mutex_trylock (&mutex1), 0);
-
- /* frame 1, return to INPUT_READY */
- mem->return_buffer (buffer1);
- /* frame 1, return to NPU_RUNNING */
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_NPU_RUNNING);
-
- /* frame 1, return to OUPUT_READY*/
- mem->return_buffer (buffer1);
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_OUTPUT_RETURN);
- /* frame 1, return to EMPTY */
- mem->return_buffer (buffer1);
-
- /** input buffer is free now, so the other thread can proceed */
- pthread_join (thread, nullptr);
-
- /** DROP_OLD */
-
- /* frame 1 */
- /* get input buffer, basic case */
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_INPUT_WRITING);
-
- /* frame 2 */
- /**
- * get input buffer, which is not available, so this will block till its
- * available
- */
- th_mode.mutex = &mutex1;
- th_mode.mode = NPUASYNC_DROP_OLD;
- pthread_create (&thread, NULL, get_buffer_wait_thread, (void *) &th_mode);
-
- /** wait for a few seconds ensuring that other thread is still waiting */
- sleep (2);
- EXPECT_NE(pthread_mutex_trylock (&mutex1), 0);
-
- /* frame 1, return to INPUT_READY */
- mem->return_buffer (buffer1);
-
- /** input buffer is free now, so the other thread can proceed */
- pthread_join (thread, nullptr);
-
- /** no cleanup required as this buffer has been dropped */
-
- /** DROP_NEW */
-
- /** WAIT */
-
- /* frame 1 */
- /* get input buffer, basic case */
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_INPUT_WRITING);
-
- /* frame 2 */
- /* get input buffer, which is not available, so we drop new buffer */
- buffer2 = mem->get_next_buffer (NPUASYNC_DROP_NEW, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer2, nullptr);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_INPUT_WRITING);
-
- /* frame 1, return to INPUT_READY */
- mem->return_buffer (buffer1);
-
- /* frame 2 */
- /* get input buffer, which is not available, so we drop new buffer */
- buffer2 = mem->get_next_buffer (NPUASYNC_DROP_NEW, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer2, nullptr);
-
- /* frame 1, return to NPU_RUNNING */
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_NPU_RUNNING);
-
- /* frame 2 */
- /* get input buffer, which is available, so no drop */
- buffer2 = mem->get_next_buffer (NPUASYNC_DROP_NEW, BUFFER_ROLE_INPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer2), BUFFER_STATE_INPUT_WRITING);
-
- /* frame 1, return to OUPUT_READY*/
- mem->return_buffer (buffer1);
- buffer1 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer1), BUFFER_STATE_OUTPUT_RETURN);
- /* frame 1, return to EMPTY */
- mem->return_buffer (buffer1);
-
- /** cleanup frame 2 */
- mem->return_buffer (buffer2);
- buffer2 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_NPU, &err);
- EXPECT_EQ (buffer_get_state(buffer2), BUFFER_STATE_NPU_RUNNING);
- mem->return_buffer (buffer2);
- buffer2 = mem->get_next_buffer (NPUASYNC_WAIT, BUFFER_ROLE_OUTPUT, &err);
- EXPECT_EQ (buffer_get_state(buffer2), BUFFER_STATE_OUTPUT_RETURN);
- mem->return_buffer (buffer2);
-
- pthread_mutex_destroy (&mutex1);
-}
-#endif
-
-/**
- * @brief main function for unit test
- */
-int
-main (int argc, char **argv)
-{
- testing::InitGoogleTest (&argc, argv);
-
- return RUN_ALL_TESTS();
-}