DSP Development Process

Overall Framework

In the Softmax operator sample provided by Horizon, it is demonstrated how to implement the functional encapsulation of DSP custom operators by scheduling the UCP framework. You can obtain the sample source code at samples/ucp_tutorial/custom_operator/dsp_sample in the OE package for synchronous reading and understanding.

P3develop

Softmax Operator Development

This section mainly introduces the process of DSP operator development by taking DSP Softmax operator development as an example, which corresponds to the samples/ucp_tutorial/custom_operator/dsp_sample/dsp_code/softmax/softmax_ivp.cc part in Horizon OE.

The complete operator development is divided into the following three steps:

  1. Calling UCP API, which is mainly responsible for task initiation and computing resource allocation.

  2. DSP operator development.

  3. Operator registration and operation.

Softmax Analysis

The Softmax operator can be divided into the following four basic calculations:

  1. Calculate the maximum value max in the input element.

  2. Calculate and update each element of the input: input = exp(input - max).

  3. Calculate the sum of the updated input.

  4. Calculate output = input / sum.

UCP API

In this section, we will show you how to use the UCP interface to apply for memory resources and initiate tasks. Taking the Softmax example, since the UCP interface only supports the two parameters of input and output, if you need to pass other parameters to the DSP, you need to perform a layer of encapsulation, pass the encapsulated "input" to the interface, and then the DSP obtains the parameters from the encapsulated "input".

In the following sample, the encapsulated "input" includes three fields, data_size is the data size, 100000 in the sample, the input field is the data address sent to the dsp, and the run_tcm_opt field indicates whether to use tcm optimization implementation.

// softmax op param
typedef struct {
  int32_t data_size;
  uint64_t input;
  uint8_t run_tcm_opt;
} hbDSPSoftmaxParam;

// #define DEBUG_RESULT

int32_t test_softmax_op(int32_t argc, char **argv) {
  LOGI("softmax op begin");

  int32_t size = 100000;
  int32_t data_size = size * sizeof(float32_t);

  hbUCPSysMem input_mem, output_mem, output_opt_mem;

  // prepare input data
  hbUCPSysMem input_data;
  HB_CHECK_SUCCESS(hbUCPMalloc(&input_data, data_size, 0),
                   "hbUCPMalloc input_data failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&input_data, &input_data),
                   "Failed to hbDSPAddrMap input_data");

  float32_t *data_ptr = static_cast<float32_t *>(input_data.virAddr);
  for (uint32_t i = 0U; i < size; ++i) {
    data_ptr[i] = 1 + i / float32_t(size);
  }

  HB_CHECK_SUCCESS(hbUCPMalloc(&output_mem, data_size, 0),
                   "hbUCPMalloc output_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&output_mem, &output_mem),
                   "Failed to hbDSPAddrMap output_mem");
  HB_CHECK_SUCCESS(hbUCPMalloc(&output_opt_mem, data_size, 0),
                   "hbUCPMalloc output_opt_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&output_opt_mem, &output_opt_mem),
                   "Failed to hbDSPAddrMap output_opt_mem");
  HB_CHECK_SUCCESS(hbUCPMalloc(&input_mem, sizeof(hbDSPSoftmaxParam), 0),
                   "hbUCPMalloc input_mem failed");
  HB_CHECK_SUCCESS(hbDSPAddrMap(&input_mem, &input_mem),
                   "Failed to hbDSPAddrMap input_mem");
  hbDSPSoftmaxParam *ptr = static_cast<hbDSPSoftmaxParam *>(input_mem.virAddr);
  ptr->data_size = data_size;
  ptr->input = input_data.phyAddr;
  ptr->run_tcm_opt = 0;

  // ctrl param
  hbUCPSchedParam sched_param;
  HB_UCP_INITIALIZE_SCHED_PARAM(&sched_param);
  sched_param.backend = HB_UCP_DSP_CORE_0;
  sched_param.priority = 0;

  // run without ping-pong
  hbUCPTaskHandle_t task{nullptr};
  HB_CHECK_SUCCESS(hbDSPRpcV2(&task, &input_mem, &output_mem, 0x1402),
                   "hbDSPRpcV2 failed");
  HB_CHECK_SUCCESS(hbUCPSubmitTask(task, &sched_param), "hbUCPSubmitTask failed");
  HB_CHECK_SUCCESS(hbUCPWaitTaskDone(task, 0), "hbUCPWaitTaskDone failed");
  HB_CHECK_SUCCESS(hbUCPReleaseTask(task), "hbUCPReleaseTask failed");

  // run without ping-pong
  ptr->run_tcm_opt = 1;
  task = nullptr;
  HB_CHECK_SUCCESS(hbDSPRpcV2(&task, &input_mem, &output_opt_mem, 0x1402),
                   "hbDSPRpcV2 failed");
  HB_CHECK_SUCCESS(hbUCPSubmitTask(task, &sched_param), "hbUCPSubmitTask failed");
  HB_CHECK_SUCCESS(hbUCPWaitTaskDone(task, 0), "hbUCPWaitTaskDone failed");
  HB_CHECK_SUCCESS(hbUCPReleaseTask(task), "hbUCPReleaseTask failed");

  float32_t *dst_data_ptr = static_cast<float32_t *>(output_mem.virAddr);
  float32_t *dst_opt_data_ptr =
      static_cast<float32_t *>(output_opt_mem.virAddr);
  bool flag{true};
  for (uint32_t i = 0U; i < size; ++i) {
    if (std::abs(dst_data_ptr[i] - dst_opt_data_ptr[i]) /
            std::abs(dst_data_ptr[i]) >
        1e-6) {
      LOGE("opt vs non-opt result is diff {}, dst {} opt {}", i,
           dst_data_ptr[i], dst_opt_data_ptr[i]);
      flag = false;
      break;
    }
  }
  if (flag) {
    LOGI("opt vs non-opt result is same");
  }

  hbDSPAddrUnmap(&input_mem);
  hbUCPFree(&input_mem);
  hbDSPAddrUnmap(&output_mem);
  hbUCPFree(&output_mem);
  hbDSPAddrUnmap(&output_opt_mem);
  hbUCPFree(&output_opt_mem);
  hbDSPAddrUnmap(&input_data);
  hbUCPFree(&input_data);
  LOGI("softmax op finish");
  return 0;
}

DSP Operator

This section will introduce how to implement the four basic operations mentioned in the previous section to realize the DSP Softmax operator.

Cadence implements some basic mathematical operations to facilitate your development. You can get the source code from the basic examples of Cadence, or directly get the compiled dependency library dsp_math from Horizon.

In order to fully utilize the hardware performance, you need to understand the DSP features and use these features (VLIW, SIMD). When developing, you can refer to the basic operations that Cadence has already implemented.

vecmaxf SIMD is implemented as follows:

/**
 * DSP find max value
 * @param[in] x: input
 * @param[in] N: length
 * @return maximum value
 */
float32_t vecmaxf(const float32_t *x, int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  valign al_px;
  xb_vecN_2xf32 vecmax0, vecmax1, vecx0, vecx1;
  vboolN_2 b_max0, b_max1;
  int32_t n, N_tail, Nb_tail;
  float32_t max;

  // ASSERT( x );
  if (N <= 0) return 0.f;

  px = (const xb_vecN_2xf32 *)x;
  al_px = IVP_LAN_2XF32_PP(px);
  vecmax0 = vecmax1 = minus_inff_.f;

  /* Main loop: process by 2*IVP_N_2 values per iteration */
  for (n = 0; n<N>> (LOG2_IVP_N_2 + 1); n++) {
    IVP_LAN_2XF32_IP(vecx0, al_px, px);
    IVP_LAN_2XF32_IP(vecx1, al_px, px);
    vecmax0 = IVP_MAXN_2XF32(vecx0, vecmax0);
    vecmax1 = IVP_MAXN_2XF32(vecx1, vecmax1);
  }
  /* Process last N%(2*IVP_N_2) values */
  N_tail = N & (2 * IVP_N_2 - 1);
  Nb_tail = N_tail * sizeof(float32_t);
  b_max0 = IVP_LTRSN_2(N_tail);
  b_max1 = IVP_LTRSN_2(N_tail - IVP_N_2);
  IVP_LAVN_2XF32_XP(vecx0, al_px, px, Nb_tail);
  IVP_LAVN_2XF32_XP(vecx1, al_px, px, Nb_tail - IVP_N_2 * sizeof(float32_t));
  IVP_MAXN_2XF32T(vecmax0, vecx0, vecmax0, b_max0);
  IVP_MAXN_2XF32T(vecmax1, vecx1, vecmax1, b_max1);
  /* Reduce maximium values from vectors to the scalar one */
#ifdef IVP_SELI_32B_ROTATE_RIGHT_16
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_16);
#endif
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_8);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_4);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_2);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  vecmax1 = IVP_SELN_2XF32I(vecmax0, vecmax0, IVP_SELI_32B_ROTATE_RIGHT_1);
  vecmax0 = IVP_MAXN_2XF32(vecmax0, vecmax1);
  max = IVP_MOVF32_FROMN_2XF32(vecmax0);

  return max;
} /* vecmaxf() */

vecexpf_max SIMD is implemented as follows:

/**
 * DSP Vectorized Floating-Point Exponential
 * The exponential (or anti-logarithm) function computes the exponential
 * value e to the power of input vector x[N], and stores the result to output
 * vector z[N].
 * @param[out] z: output
 * @param[in] x: input
 * @param[in] N: length
 * @param[in] max_value: max_value
 * @return maximum value
 */
void vecexpf_max(float32_t *z, const float32_t *x, int32_t N,
                 float32_t max_value) {
  const xb_vecN_2xf32 *restrict px = (const xb_vecN_2xf32 *)x;
  xb_vecN_2xf32 *restrict pz = (xb_vecN_2xf32 *)z;
  xtfloat *restrict ptbl = (xtfloat *)expftblf;

  // check correct
  xb_vecN_2xf32 xmax = max_value;
  xb_vecN_2xf32 xin, xin2, txin, zout;
  xb_vecN_2xf32 p0, p1, p2, p3, p4, p5, p6;
  xb_vecN_2xf32 scl1, scl2;
  xb_vecN_2x32v t, exp_fract, exp_int, e1, e2;
  xb_vecN_2x64w W;
  xb_vecNx16 invln2;
  valign xa, za;
  vboolN_2 b_nan, b_max, b_inf;
#if EXPF_ERRH != 0
  vboolN_2 b_edom, b_erange;
  xb_int32v SCF; /* Floating-point Status and Control Register values. */
#endif
  int32_t n;

  /* common argument checks */
  // NASSERT(x);
  // NASSERT(z);
  if (N <= 0) return;

  /* load 1/ln(2) constant in Q30 */
  invln2 =
      IVP_MOVNX16_FROMN_2X32(IVP_LSN_2X32_I((const int32_t *)&invln2_Q30, 0));

  za = IVP_ZALIGN();
  xa = IVP_LAN_2XF32_PP(px);

  for (n = 0; n<(N + IVP_N_2 - 1)>> LOG2_IVP_N_2; n++) {
    IVP_LAVN_2XF32_XP(xin, xa, px, (uint8_t *)x + N * 4 - (uint8_t *)px);

    txin = xin;
    /* Check input for values that are out of domain/range */
    b_nan = IVP_UNN_2XF32(xin, xin);              /* x==NaN                */
    b_max = IVP_ULEN_2XF32(expfminmax[1].f, xin); /* x>=88.72284 or x==NaN */
    b_inf = IVP_UEQN_2XF32(xin, plus_inff.f);     /* x==+Inf or x==NaN     */

    /* Limit input values to [-128;+127] and replace NaNs *
     * with some numbers to avoid unnecessary exceptions  */
    xin = IVP_SUBN_2XF32(xin, xmax);
    xin = IVP_MOVN_2XF32T(127.0f, xin, b_max);
    xin = IVP_MAXN_2XF32(xin, -128.0f);
    /* Convert the input to Q24 and scale to 1/ln(2) */
    t = IVP_TRUNCN_2XF32(xin, 24);
    W = IVP_MULN_2X16X32_0(invln2, t);
    IVP_MULAHN_2X16X32_1(W, invln2, t); /* Q24*Q30->Q54 */

    /* Separate input to positive fractional part and integer part */
    exp_fract = IVP_PACKVRNRN_2X64W(W, 22);    /* Q54->Q32 */
    exp_fract = IVP_SRLIN_2X32U(exp_fract, 1); /* Q32->Q31 */
    exp_int = IVP_PACKHN_2X64W(W);             /* Q54->Q22 */
    exp_int = IVP_SRLIN_2X32(exp_int, 22);     /* Q22->Q0 */

    /* compute 2^fract in floating-point format */
    xin =
        IVP_FLOATN_2X32(exp_fract, 31); /* scale fraction part by 2^-31 with */
    /* conversion from int32_t to float32_t */

    /* Pass input to the output if x==+INF or x==NaN */
    xin = IVP_MOVN_2XF32T(txin, xin, b_inf);
    /* load polynomial coefficients */
    IVP_LSRN_2XF32_IP(p0, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p1, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p2, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p3, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p4, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_IP(p5, ptbl, sizeof(float32_t));
    IVP_LSRN_2XF32_XP(p6, ptbl, -6 * (int32_t)sizeof(float32_t));
    /* compute polynomial using combination *
     * of Estrin`s and Horner schemes       */
    xin2 = IVP_MULN_2XF32(xin, xin);
    IVP_MULAN_2XF32(p1, xin, p0);
    IVP_MULAN_2XF32(p3, xin, p2);
    IVP_MULAN_2XF32(p5, xin, p4);

    IVP_MULAN_2XF32(p3, xin2, p1);
    IVP_MULAN_2XF32(p5, xin2, p3);

    IVP_MULAN_2XF32(p6, xin, p5);

    /* Apply integer exponential part to the result */
    exp_int = IVP_ADDN_2X32(exp_int, 254);
    e1 = IVP_SRLIN_2X32(exp_int, 1);
    e2 = IVP_SUBN_2X32(exp_int, e1);
    e1 = IVP_SLLIN_2X32(e1, 23);
    e2 = IVP_SLLIN_2X32(e2, 23);
    scl1 = IVP_MOVN_2XF32_FROMN_2X32(e1);
    scl2 = IVP_MOVN_2XF32_FROMN_2X32(e2);
    zout = IVP_MULN_2XF32(p6, scl1);
    zout = IVP_MULN_2XF32(zout, scl2);

    IVP_SAVN_2XF32_XP(zout, za, pz, (uint8_t *)z + N * 4 - (uint8_t *)pz);
  }
  IVP_SAPOSN_2XF32_FP(za, pz);
} /* vecexpf() */

vecsum SIMD is implemented as follows:

/**
 * DSP Vectorized sum value
 * @param[in] x: input
 * @param[in] N: length
 * @return sum of x[N] value
 */
static float32_t vecsum(const float32_t *x, int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  valign al_px;
  xb_vecN_2xf32 vecsum0, vecsum1, vecx0, vecx1;
  vboolN_2 b_sum0, b_sum1;
  int32_t n, N_tail, Nb_tail;
  float32_t sum;

  // ASSERT(x);
  if (N <= 0) return 0.f;
  px = (const xb_vecN_2xf32 *)x;
  al_px = IVP_LAN_2XF32_PP(px);
  vecsum0 = vecsum1 = 0.f;
  for (n = 0; n<N>> (LOG2_IVP_N_2 + 1); n++) {
    IVP_LAN_2XF32_IP(vecx0, al_px, px);
    IVP_LAN_2XF32_IP(vecx1, al_px, px);
    vecsum0 = IVP_ADDN_2XF32(vecx0, vecsum0);
    vecsum1 = IVP_ADDN_2XF32(vecx1, vecsum1);
  }
  N_tail = N & (2 * IVP_N_2 - 1);
  Nb_tail = N_tail * sizeof(float32_t);
  b_sum0 = IVP_LTRSN_2(N_tail);
  b_sum1 = IVP_LTRSN_2(N_tail - IVP_N_2);
  IVP_LAVN_2XF32_XP(vecx0, al_px, px, Nb_tail);
  IVP_LAVN_2XF32_XP(vecx1, al_px, px, Nb_tail - IVP_N_2 * sizeof(float32_t));
  IVP_ADDN_2XF32T(vecsum0, vecx0, vecsum0, b_sum0);
  IVP_ADDN_2XF32T(vecsum1, vecx1, vecsum1, b_sum1);

  vecsum0 = IVP_ADDN_2XF32(vecsum0, vecsum1);
  sum = IVP_RADDN_2XF32(vecsum0);
  return sum;
}

The division operation can be transformed into a multiplication operation, and the multiplication operation is easier to implement and has better performance.

/**
 * DSP Vectorized multiply
 * @param[out] z: output
 * @param[in] x1: multiply left input
 * @param[in] x2: multiply right input
 * @param[in] N: length
 */
static void vecmul(float32_t *z, const float32_t *x1, const float32_t x2,
                   int32_t N) {
  const xb_vecN_2xf32 *restrict px;
  xb_vecN_2xf32 *restrict pz;
  valign al_px, al_pz;

  px = (const xb_vecN_2xf32 *)x1;
  pz = (xb_vecN_2xf32 *)z;
  xb_vecN_2xf32 px2 = x2;

  al_px = IVP_LAN_2XF32_PP(px);
  al_pz = IVP_ZALIGN();

  int32_t n, modN;
  modN = (N & (IVP_N_2 - 1)) * sizeof(float32_t);
  xb_vecN_2xf32 in, out;
  for (n = 0; n<N>> LOG2_IVP_N_2; n++) {
    IVP_LAN_2XF32_IP(in, al_px, px);
    out = IVP_MULN_2XF32(in, px2);
    IVP_SAN_2XF32_IP(out, al_pz, pz);
  }

  IVP_LAN_2XF32_IP(in, al_px, px);
  out = IVP_MULN_2XF32(in, px2);
  IVP_SAVN_2XF32_XP(out, al_pz, pz, modN);
  IVP_SAPOSN_2XF32_FP(al_pz, pz);
}

The complete Softmax implementation is as follows:

static int32_t dsp_softmax(float32_t *input, int32_t length,
                           float32_t *output) {
  float32_t max_value = vecmaxf(input, length);
  DSP_LOGD("max_value %f", max_value);
  vecexpf_max(output, input, length, max_value);
  float32_t sum = vecsum(output, length);
  DSP_LOGD("sum %f", sum);

  int32_t i = 0;
  float32_t div = 1 / sum;
  vecmul(output, output, div, length);
  return 0;
}

The above Softmax implementation directly accesses DDR. Since J6 is not configured with vector cache, direct access to DDR memory is very time-consuming. A common optimization implementation is to divide the data into tiles, move them to TCM (TCM has the same latency as cache) through idma, and directly access TCM for calculation. In addition, in order to parallelize the calculation with the idma transfer operation, the DSP is equipped with two TCMs to implement ping pong DMA operations, as shown in the figure below.

pingpong-idma

The reference code for the implementation of ping pong DMA is as follows:

/**
 * DSP tile Vectorized max
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] max: max value addr: max value addr
 * @param[in] param: input param
 */
void tile_max(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *max, float32_t param) {
  float32_t max_value = vecmaxf(in_addr, size);
  DSP_LOGD("max_value %f, size %d", max_value, size);
  *max = max_value > *max ? max_value : *max;
}

/**
 * DSP tile Vectorized sum
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] sum: sum value addr: sum value addr
 * @param[in] param: input param
 */
void tile_sum(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *sum, float32_t param) {
  vecexpf_max(out_addr, in_addr, size, param);
  *sum += vecsum(out_addr, size);
}

/**
 * DSP tile Vectorized mul
 * @param[out] output_addr: tcm output addr
 * @param[in] in_addr: tcm input addr
 * @param[in] size: tcm compute size
 * @param[out] out: out value addr: out value addr
 * @param[in] param: input param
 */
void tile_mul(float32_t *out_addr, float32_t *in_addr, int32_t size,
              float32_t *out, float32_t param) {
  float32_t div = 1.0f / param;
  vecmul(out_addr, in_addr, div, size);
}

/**
 * DSP softmax ping-pong framework
 * @param[out] dst: optional
 * @param[in] src
 * @param[in] length
 * @param[in] func: framework tile compute func
 * @param[out] func_out: func output param
 * @param[in] func_in: func input param
 * @param[in] with_out_idma: whether need to copy back output
 * @return 0 if success, return defined error code otherwise
 */
int32_t hb_dsp_ping_pong_frame(float32_t *dst, float32_t *src, void *src_idma[],
                               void *dst_idma[], int32_t length,
                               void (*func)(float32_t *, float32_t *, int32_t,
                                            float32_t *, float32_t),
                               float32_t *func_out, float32_t func_in,
                               bool with_out_idma = true) {
  int32_t size[] = {0, 0};
  int32_t vret_in[2];
  int32_t vret_out[2];
  int32_t i = 0;
  int32_t block_start = 0;
  int32_t output_start = 0;
  int32_t ping_pong = 0;

  int32_t block = XT_MIN(TILE_SIZE >> 2, length);
  int32_t block_size = block << 2;
  size[0] = block;
  float32_t sum = 0.f;
  IDMA_COPY(HB_IDMA_CH0, vret_in[0], src, src_idma[0], block_size)
  if (vret_in[0] < 0) {
    DSP_LOGE("copy task failed!");
  }

  block_start += block;
  if (block_start < length) {
    block = XT_MIN(block, length - block_start);
    block_size = block << 2;
    size[1] = block;

    IDMA_COPY(HB_IDMA_CH1, vret_in[1], src + block_start, src_idma[1],
              block_size)
    if (vret_in[1] < 0) {
      DSP_LOGE("copy task failed!");
    }
    block_start += block;
  }

  while (i < length) {
    i += size[ping_pong];
    IDMA_WAIT(ping_pong, vret_in[ping_pong])
    if (with_out_idma) {
      IDMA_WAIT(ping_pong, vret_out[ping_pong])
    }
    func((float32_t *)dst_idma[ping_pong], (float32_t *)src_idma[ping_pong],
         size[ping_pong], func_out, func_in);

    if (with_out_idma) {
      IDMA_COPY(ping_pong, vret_out[ping_pong], dst_idma[ping_pong],
                dst + output_start, size[ping_pong] << 2)
      if (vret_out[ping_pong] < 0) {
        DSP_LOGE("Fail to copy softmax output, status %d", vret_out[ping_pong]);
        return -1;
      }
      output_start += size[ping_pong];
    }

    if (block_start < length) {
      block = XT_MIN(block, length - block_start);
      block_size = block << 2;
      size[ping_pong] = block;

      IDMA_COPY(ping_pong, vret_in[ping_pong], src + block_start,
                src_idma[ping_pong], block_size)
      if (vret_in[ping_pong] < 0) {
        DSP_LOGE("copy task failed!");
      }
      block_start += block;
    }
    ping_pong ^= 0x1;
  }
  if (with_out_idma) {
    IDMA_WAIT(ping_pong ^ 0x01, vret_out[ping_pong ^ 0x01])
  }
  return 0;
}

The complete DSP softmax implementation is as follows:

#define PROF

/*
 * hb_dsp_softmax have two implementation, ping-pong and direct call ddr
 * 1. ping-pong: use idma to transfer ddr data to tcm, then use tile compute(simd optimization)
 * 2. direct ddr: use simd(same with tile compute) to directly load and store ddr
 * 3. profiling: ping-pong use 5154293 cycles, direct ddr use 7648254 cycles on j6 in this scenario
 */
int32_t hb_dsp_softmax(void *input, void *output, void *tm) {
  DSP_LOGD("enter hb_dsp_softmax\n");
  hbDSPSoftmaxParam *ptr = (hbDSPSoftmaxParam *)(input);
  float32_t *src = (float32_t *)hb_dsp_mem_map(ptr->input, ptr->data_size);
  PRT_IF_COND_RETURN(MAP_FAILED == src, HB_ERR_MMAP_FAILED)
  float32_t *dst = (float32_t *)(output);

  // ping-pong optimization
  if (ptr->run_tcm_opt) {
    xvTileManager *tm_ = (xvTileManager *)tm;
    float32_t *src_0 = (float32_t *)(xvAllocateBuffer(
        tm_, TILE_SIZE, XV_MEM_BANK_COLOR_0, IVP_2N));
    float32_t *dst_0 = src_0;
    float32_t *src_1 = (float32_t *)(xvAllocateBuffer(
        tm_, TILE_SIZE, XV_MEM_BANK_COLOR_1, IVP_2N));
    float32_t *dst_1 = src_1;
    void *src_idma[] = {src_0, src_1};
    void *dst_idma[] = {dst_0, dst_1};
    int32_t length = ptr->data_size >> 2;
    DSP_LOGD("length is %d\n", length);

#ifdef PROF
    TIME_STAMP(cycles_start);
#endif
    // find max
    DSP_LOGD("find max\n");
    float32_t max_value = 0.f;
    hb_dsp_ping_pong_frame(dst, src, src_idma, dst_idma, length, tile_max,
                           &max_value, 0, false);
    DSP_LOGD("max_value %f", max_value);

    // exp & sum
    DSP_LOGD("exp & sum");
    float32_t sum_value = 0.f;
    hb_dsp_ping_pong_frame(dst, src, src_idma, dst_idma, length, tile_sum,
                           &sum_value, max_value, true);
    DSP_LOGD("sum_value %f", sum_value);

    // mul
    DSP_LOGD("mul");
    hb_dsp_ping_pong_frame(dst, dst, src_idma, dst_idma, length, tile_mul, 0,
                           sum_value, true);

    xvFreeBuffer(tm_, (void *)src_0);
    xvFreeBuffer(tm_, (void *)src_1);

#ifdef PROF
    TIME_STAMP(cycles_stop);
    self_cycles = cycles_stop - cycles_start;
    DSP_LOGI("find softmax ping-pong tcm is %d cycles", self_cycles);
#endif
  } else {
    // direct call ddr, because use simd, so not need flush cache
#ifdef PROF
    TIME_STAMP(cycles_start);
#endif
    int32_t N = ptr->data_size >> 2;
    int32_t ret = dsp_softmax(src, N, dst);
    if (ret != 0) {
      DSP_LOGE("Run softmax op fail:%d", ret);
      return ret;
    }

#ifdef PROF
    TIME_STAMP(cycles_stop);
    self_cycles = cycles_stop - cycles_start;
    DSP_LOGI("find softmax ddr tcm is %d cycles", self_cycles);
#endif  // PROF
  }
  hb_dsp_mem_unmap((uint32_t)src);

  return 0;
}

DSP Operator Register

After the operator development is completed, register the hb_dsp_softmax operator by calling the hb_dsp_register_fn interface. After registration is completed, compile the DSP image.

typedef int (*handle_fn)(void *input, void *output, void *tm);

/**
 * register custom op, should be called before hb_dsp_start
 * @param[in] latency: op latency(microseconds), if no latency info, set 0
 * @return 0 if success, return defined error code otherwise
 */
int hb_dsp_register_fn(int cmd, handle_fn handle, int latency);
Note

If it is x86 simulation running, there is no need to start the image. If it is running on the board, you need to execute the script dsp_deploy.sh to start the image.