TISP User Guide
Attention
  • Features listed below are early versions and should be considered as "experimental".
  • Users can evaluate the feature, however the feature is not fully tested at TI side.
  • TI would not support these feature on public e2e.
  • Experimental features will be enabled with limited examples and SW modules.

TISP (TI signal processing) is a software library that strives to abstract away the software and the hardware complexity of programming the C7x.

Introduction

A typical signal chain is shown in the figure below. Basically, a collection of kernels (nodes) stitched together. A set of these kernels in the signal chain are grouped for DMA (supernode). Typically, in user applications, the complete data needs to be processed by nodes encapsulated inside the supernode before calling the next node in the signal chain. Nodes (kernels) themselves perform compute using data in L2SRAM. Data movement from DDR to L2SRAM (DMA) is accomplished via DRU (data routing unit) available within the C7x.

Typical Signal Chain

Node-level Abstraction

In order to create a common baseline for kernels from various libraries, we first create a virtual class TISP::virtualOp. Doing so, allows us to create a class for every kernel in a given library that is derived from the base virtual class TISP::virtualOp. Please note that node-level abstraction is not available for all kernels of a given library. Please refer to DSPLIB Nodes for supported nodes.

Node-Level Abstraction

Employing this methodology enables us to string a bunch of nodes together through a custom vector datatype, TISP::opVec. An example of the matrix transpose kernel from DSPLIB described as a class TISP::DSPLIB::matTrans is shown below. Please refer to the DSPLIB documentation for more details about the data structures pertaining to DSPLIB.

8 
9 template <typename T, dmaDir_t dmaDir>
10 matTrans<T, dmaDir>::matTrans(T *_pX, T *_pY, size_t _width, size_t _height, size_t _inPitch, size_t _outPitch)
11  : pX(_pX), pY(_pY)
12 {
13 
14  /***********************************************************************/
15  /* Initialize data structures required by DSPLIB_matrTrans from DSPLIB */
16  /***********************************************************************/
17 
18  DSPLIB_matTransInitArgs kerInitArgs;
19  kerInitArgs.dimX = _width;
20  kerInitArgs.dimY = _height;
21  kerInitArgs.funcStyle = DSPLIB_FUNCTION_OPTIMIZED;
22 
23  int32_t handleSize = DSPLIB_matTrans_getHandleSize(&kerInitArgs);
24  handle = memalign(128, handleSize);
25 
26  DSPLIB_bufParams2D_t bufParamsIn, bufParamsOut;
27 
28  bufParamsIn.data_type = DSPLIB_element_dType<T>::value;
29  bufParamsIn.dim_x = _width;
30  bufParamsIn.dim_y = _height;
31  bufParamsIn.stride_y = _inPitch;
32 
33  bufParamsOut.data_type = DSPLIB_element_dType<T>::value;
34  bufParamsOut.dim_x = _height;
35  bufParamsOut.dim_y = _width;
36  bufParamsOut.stride_y = _outPitch;
37 
38  /******************************************************************************/
39  /* Call init function from DSPLIB to for matrix transpose to set SE/SA params */
40  /******************************************************************************/
41 
42  DSPLIB_matTrans_init(handle, &bufParamsIn, &bufParamsOut, &kerInitArgs);
43 
44  /***************************************************/
45  /* Set pingPong offsets and flags to default value */
46  /***************************************************/
47 
48  pingPongOffsetIn = (_height * _inPitch) / sizeof(T);
49  pingPongOffsetOut = (_width * _outPitch) / sizeof(T);
50 
51  pingPongFlagIn = 0;
52  pingPongFlagOut = 0;
53 }
54 

Connecting and Executing Nodes

The data flow while connecting nodes is depicted through the pointer addresses shared between the nodes when creating a node through its class constructor. Whilst the compute flow is represented via the order in which the nodes are pushed back into TISP::opVec. Thus allowing us to create any given signal chain as a graph.

Connecting Nodes

An example code snippet is shown below for connecting creating and connecting nodes together, taken from TISP_fft1dBatched_test.cpp. After creating nodes via constructors and pushing them back into TISP::opVec with the desired order, TISP::execute::graph can be called to iterate through the vector of nodes to call the execute function pertaining to a given node.

45 
46  auto k0 = std::make_unique<TISP::misc::blockCopy<float, TISP::NO_DMA>>(pX0, pY0, NUM_POINTS * 2, NUM_CHANNELS);
47 
48  auto k1 = std::make_unique<TISP::FFTLIB::fft1dBatched<float, TISP::NO_DMA>>(pY0, pY1, NUM_POINTS, NUM_CHANNELS);
49 
50  auto k2 = std::make_unique<TISP::DSPLIB::sqr<float, TISP::NO_DMA>>(pY1, pY2, 2 * NUM_POINTS * NUM_CHANNELS);
51 
52  auto myOpVec = new TISP::opVec();
53 
54  myOpVec->push_back(std::move(k0));
55  myOpVec->push_back(std::move(k1));
56  myOpVec->push_back(std::move(k2));
#define NUM_POINTS
#define NUM_CHANNELS
std::vector< std::unique_ptr< virtualOp > > opVec

Supernode-level Abstraction

We employ a similar strategy that was used to abstract nodes for supernodes as well. In the case of supernodes, we could have various variants such as TISP::SuperNode::i2d_to_o2d, TISP::SuperNode::i2d_to_o2d, etc. Further styles of supernodes will be added as we expand the scope of this library/framework. Note that variants of supernode are derived from a non-virtual base class TISP::SuperNode::superNode.

Supernode-Level Abstraction

An example code snippet for one input one output supernode is shown below. The DRU parameters are programmed to access the input and output DDR space in a 2D form.

12 
13 template <typename dTypeIn, typename dTypeOut>
14 i2d_to_o2d<dTypeIn, dTypeOut>::i2d_to_o2d(std::unique_ptr<opVec> &&_myOpVec,
15  dTypeIn *_pIn,
16  dTypeOut *_pOut,
17  dTypeIn *_pInBlock,
18  dTypeOut *_pOutBlock,
19  size_t _width,
20  size_t _height,
21  size_t _blockWidth,
22  size_t _blockHeight,
23  size_t _inPitch,
24  size_t _outPitch,
25  size_t _numChannels)
26  : superNode(_numChannels, NUM_IN_CHANNELS, NUM_OUT_CHANNELS), myOpVec(std::move(_myOpVec)), pIn(_pIn), pOut(_pOut),
27  pInBlock(_pInBlock), pOutBlock(_pOutBlock)
28 
29 {
30 
31  int32_t retVal = UDMA_SOK;
32 
33  /***********************/
34  /* Configure input DMA */
35  /***********************/
36 
37  // set transfer dimenssion structure
38  dmaUtils<dTypeIn>::dmaAutoIncSetupXferPropIn2D(_width, _height, _blockWidth, _blockHeight, _inPitch, _outPitch,
39  &transferDimIn);
40  // assign channel number
41  chIdIn[0] = 0;
42  /* chIdIn[0] = dmaChOffset::globalChOffset; */
43  /* dmaChOffset::globalChOffset += 1; */
44 
45  /***********************/
46  /* Configure output DMA */
47  /***********************/
48 
49  // set transfer dimenssion structure
50  dmaUtils<dTypeOut>::dmaAutoIncSetupXferPropOut2D(_width, _height, _blockWidth, _blockHeight, _inPitch, _outPitch,
51  &transferDimOut);
52  // assign channel number
53  chIdOut[0] = 1;
54  /* chIdOut[0] = dmaChOffset::globalChOffset; */
55  /* dmaChOffset::globalChOffset += 1; */
56 }
#define NUM_OUT_CHANNELS
#define NUM_IN_CHANNELS

Connecting and Executing Supernodes

Connecting and execution supernodes is also similar to nodes. The execution order of the kernels inside the supernode is determined by the TISP::opVec datastructure passed to the supernode constructor as argument.

Connecting Supernodes

Please refer to 2D FFT example section below for more details.

2D FFT Example

The example has two supernodes connected together as shown in the figure below.

2D FFT Example

Shown below is the code snippet that depicts the 2D FFT graph. The code snippet is taken from TISP_fft2d_superNode_test.cpp.

106 
107  auto k0 = std::make_unique<TISP::FFTLIB::fft1dBatched<float, TISP::DMA_IN>>(pInputBlock, pTempBlock, numPoints,
108  blockHeight);
109 
110  auto k1 = std::make_unique<TISP::DSPLIB::matTrans<double, TISP::DMA_OUT>>(
111  (double *) pTempBlock, (double *) pOutputBlock, blockWidth >> 1, blockHeight, blockWidth * sizeof(double) >> 1,
112  blockHeight * sizeof(double));
113 
114  auto myOpVec0 = make_unique<TISP::opVec>();
115 
116  myOpVec0->push_back(std::move(k0));
117  myOpVec0->push_back(std::move(k1));
118 
119  auto s0 = std::make_unique<TISP::SuperNode::i2d_to_o2dTranspose<double, double>>(
120  std::move(myOpVec0), (double *) input, (double *) tempOut, (double *) pInputBlock, (double *) pOutputBlock,
121  numPoints, numChannels, blockWidth >> 1, blockHeight, (numPoints) * sizeof(double), numChannels * sizeof(double),
123 
124  auto k2 = std::make_unique<TISP::FFTLIB::fft1dBatched<float, TISP::DMA_IN_OUT>>(pInputBlock, pOutputBlock,
125  numChannels, blockHeight);
126 
127  auto myOpVec1 = std::make_unique<TISP::opVec>();
128 
129  myOpVec1->push_back(std::move(k2));
130 
131  auto s1 = std::make_unique<TISP::SuperNode::i2d_to_o2d<float, float>>(
132  std::move(myOpVec1), tempOut, output, pInputBlock, pOutputBlock, numChannels << 1, numPoints, numChannels << 1,
133  blockHeight, (numChannels << 1) * sizeof(float), (numChannels << 1) * sizeof(float), (TOTAL_DMA_CHANNELS));
134 
135  /*********************************************************************/
136  /* */
137  /*********************************************************************/
138 
139  auto mySuperNodeVec = new TISP::SuperNode::superNodeVec();
140 
141  mySuperNodeVec->push_back(std::move(s0));
142  mySuperNodeVec->push_back(std::move(s1));
143 
#define TOTAL_DMA_CHANNELS
std::vector< std::unique_ptr< TISP::SuperNode::superNode > > superNodeVec

The use of smart pointers allows us to destroy the graph and the memory allocated for the nodes within the graph with minimal code overhead.

Disclaimer

See also TI Disclaimer.