复数乘法指令

  TI的编译器提供的一些内联函数中可以直接调用复数乘法的汇编指令(CMPYSP,只有C66x DSP支持),完成复数乘法运算。

  src1和src2是连续的两个32位寄存器组成的register pair,dst是由四个连续的32位寄存器组成的register quadruplets。这里只是做了乘法运算。

  然后用DADDSP指令可以对上面的结果中的实数部分和虚数部分分别求和,即可实现复数乘法。

(a+bi)(c+di)=acbd+(ad+bc)i\left( {a + b{\rm{i} } } \right)\left( {c + d{\rm{i} } } \right) = ac - bd + \left( {ad + bc} \right){\rm{i} }

  另外编译器也提供了一种快速实现复共轭乘法的方式,即p×qp\times q^*。乘数中需要求复共轭的数qq放在src1寄存器对中作为a+bia+b\rm{i},另一个乘数pp放在src2寄存器对中,依次完成CMPYSP和DSUBSP即可得到p×qp\times q^*的结果。

(abi)(c+di)=ac+bd+(adbc)i\left( {a - b{\rm{i} } } \right)\left( {c + d{\rm{i} } } \right) = ac + bd + \left( {ad - bc} \right){\rm{i} }

  总结起来就是用_complex_conjugate_mpysp(m1, m2)这个函数的时候,第一个参数m1是那个取复共轭的复数。
  另外,从运算效率上考虑,如果一个复数在存储器中的存放方式与它在寄存器中的存放方式相同,那么就可以只用一条LDDW指令就从存储器中将一个64bit表示的复数直接load到寄存器中。可以看到寄存器中实部是存在高32位而虚部是存在低32位,所以建议存储器中也按照这样的方式存放(而一般FFT支持的复数格式是实部在低地址,虚部在高地址,是相反的,需要注意!)

Cache映射方式

  Cache的映射方式包括直接映射,全相联映射和组相联映射,后者是前两者相结合的产物。C6678的L1P Cache采用的是直接映射,存储器中的code在L1P Cache中只有唯一确定的位置。
  C6678的L1D Cache采用的是两路组相联映射(two-way set associative cache),一个set里面有两个line frame,存储器中的数据映射到固定的set,但可以是任意的line frame。

  C6678的cache line frame是64字节,所以地址中的Offset字段固定为6bits。一个set是128字节,如果L1D的Cache大小为4kB,那么L1D Cache可以分为32个set,所以Set字段为5bits,剩下的就是Tag字段21bits。

  当L1D Cache设置为32kB时,set的数量也就变为了原来的8倍,因此Set字段需要8bits,相应的Tag字段减少为18bits。

TI Data Type

  调试过程中经常需要预先往存储器中加载数据,可以通过Memory Browser窗口里的Load Memory实现。

  需要加载的数据预先存在在一个“xxx.dat”文件里,按照TI Data的格式进行存放,在加载的时候CCS就会根据文件头的信息把数据加载到指定的位置。

  帧头的数都是十六进制表示的!可以在CCS的Help->Help Comtents里搜索“data format”找到相关的介绍。

  数据格式“9”表示一种比较新的数据格式说明方式,具体数据格式由最后一个数决定。数据个数就是数据的行数,因为一行一个数据。具体数据格式如下表:

具体数据格式(十六进制) 含义
0 64bit Hex TI-style
1 64bit Hex C-style (带0x前缀)
2 64bit Floating Point
3 Exponential Float (指数表示的浮点数)
4 32bit Hex TI-style
5 32bit Hex C-style
6 32bit Signed Int
7 32bit Unsigned Int
8 32bit Binary
9 32bit Floating Point
A 32bit Exponential Float
B 16bit Hex TI-style
C 16bit Hex C-style
D 16bit Signed Int
E 16bit Unsigned Int
F 16bit Binary
10 8bit Hex TI-style
11 8bit Hex C-style
12 8bit Signed Int
13 8bit Unsigned Int
14 8bit Binary
15 Character
16 Packed Char

QDMA的使用

  有时候需要手动启动DMA并且随时需要对PaRAM进行修改,而不是等待外部事件触发,那么就可以用QDMA来实现。QDMA能够在写完PaRAM时就启动DMA传输。实际应用过程中可以有这几点需要考虑:

  1. 硬件初始化
  2. 中断服务函数
  3. 启动传输

  下面是C6678上用于实现矩阵转置的一个类。三个成员函数分别对应我提到的三点要考虑的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef CTSPSMANAGER_H
#define CTSPSMANAGER_H

#include <csl_edma3.h>

#define TPSP_PARAM_NUM (10)
#define TSPS_QDMA_CHANNEL (0)
#define TSPS_TCC_CODE (8)
#define TSPS_SYSEVT_EDMA3R0 (38)
#define TSPS_REGION CSL_EDMA3_REGION_0

class CTspsManager
{
private:
CSL_Edma3Handle m_hEdma;
CSL_Edma3Obj m_Obj;
Bool m_bInitDone;
int m_nCicId;

public:
CTspsManager();
~CTspsManager();

int HwInit(int coreId);
void startTranspose(
void *restrict pSrc,
void *restrict pDst,
int nSrcWidth,
int nSrcHeight,
size_t nUnit);
void ClearInterruptFlag();
};

#endif

  硬件初始化过程中,因为要配置中断,所以不同的核需要设置对应的片上中断控制器(CIC)。QDMA通道绑定队列与PaRAM,使能通道。同时也需要使能PaRAM中TCC code对应的DMA通道,和这个DMA通道的中断使能。清中断的时候需要清系统中断和EMDA的中断挂号寄存器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
int CTspsManager::HwInit(int coreId)
{
CSL_Status status;
CSL_Edma3HwSetup hwSetup;
UInt nChannel;
CSL_Edma3HwDmaChannelSetup hwDmaSetup[] = CSL_EDMA3_DMACHANNELSETUP_DEFAULT;
CSL_Edma3HwQdmaChannelSetup hwQdmaSetup[] = CSL_EDMA3_QDMACHANNELSETUP_DEFAULT;

if(coreId < 0 || coreId > 7)
return -1;

if(m_bInitDone)
return 0;

m_hEdma = CSL_edma3Open(&m_Obj, CSL_EDMA3CC_0, NULL, &status);
assert(m_hEdma != NULL);

//--------------------- Hardware Setup --------------------------

// DMA setup
hwDmaSetup[TSPS_TCC_CODE].paramNum = TPSP_PARAM_NUM;
hwDmaSetup[TSPS_TCC_CODE].que = CSL_EDMA3_QUE_0;

hwSetup.dmaChaSetup = hwDmaSetup;
// end of DMA setup

// QDMA setup
hwQdmaSetup[TSPS_QDMA_CHANNEL].paramNum = TPSP_PARAM_NUM;
hwQdmaSetup[TSPS_QDMA_CHANNEL].que = CSL_EDMA3_QUE_0;
hwQdmaSetup[TSPS_QDMA_CHANNEL].triggerWord = CSL_EDMA3_TRIGWORD_DEFAULT;
hwSetup.qdmaChaSetup = hwQdmaSetup;
// end of QDMA setup

CSL_edma3HwSetup(m_hEdma, &hwSetup);
//--------------------- end of Hardware Setup --------------------------

//--------------------- Queue Priority Setup --------------------------
CSL_edma3SetEventQueuePriority(m_hEdma, CSL_EDMA3_QUE_0, CSL_EDMA3_QUE_PRI_0);
CSL_edma3SetEventQueuePriority(m_hEdma, CSL_EDMA3_QUE_1, CSL_EDMA3_QUE_PRI_1);
CSL_edma3SetEventQueuePriority(m_hEdma, CSL_EDMA3_QUE_2, CSL_EDMA3_QUE_PRI_2);
CSL_edma3SetEventQueuePriority(m_hEdma, CSL_EDMA3_QUE_3, CSL_EDMA3_QUE_PRI_3);
//--------------------- end of Queue Priority Setup --------------------------

CSL_edma3QdmaRegionAccessDisable(m_hEdma, TSPS_REGION, 0xF);
CSL_edma3QdmaRegionAccessEnable(m_hEdma, TSPS_REGION, 1 << TSPS_QDMA_CHANNEL);
CSL_edma3DmaRegionAccessDisable(m_hEdma, TSPS_REGION, 0xFFFFFFFF, 0xFFFFFFFF);
CSL_edma3DmaRegionAccessEnable(m_hEdma, TSPS_REGION, 1 << TSPS_TCC_CODE, 0);
CSL_edma3InterruptLoEnable(m_hEdma, TSPS_REGION, 1 << TSPS_TCC_CODE);

//--------------------- end of Region Access Setup --------------------------

CSL_edma3QDMAChannelEnable(m_hEdma, TSPS_REGION, TSPS_QDMA_CHANNEL);
CSL_edma3ClearDMAChannelEvent(m_hEdma, TSPS_REGION, TSPS_TCC_CODE);
CSL_edma3DMAChannelEnable(m_hEdma, TSPS_REGION, TSPS_TCC_CODE);

// CIC0 is used to connect EMDA finish interrupt to CPU
m_nCicId = (coreId < 4) ? 0 : 1;
nChannel = coreId % 4 * 11 + 33; // attach to host interrupt ID 22
CpIntc_clearSysInt(m_nCicId, TSPS_SYSEVT_EDMA3R0);
CpIntc_enableSysInt(m_nCicId, TSPS_SYSEVT_EDMA3R0);
CpIntc_mapSysIntToHostInt(m_nCicId, TSPS_SYSEVT_EDMA3R0, nChannel);
CpIntc_enableHostInt(m_nCicId, nChannel);
CpIntc_enableAllHostInts(m_nCicId);

return 0;
}

void CTspsManager::startTranspose(
void *restrict pSrc,
void *restrict pDst,
int nSrcWidth,
int nSrcHeight,
size_t nUnit)
{
CSL_Status status;
CSL_Edma3ParamSetup paramSetup;
CSL_Edma3ParamHandle hParam;

Cache_wb(pSrc, nSrcWidth * nSrcHeight * nUnit, Cache_Type_ALLD, TRUE);

hParam = CSL_edma3GetParamHandle(m_hEdma, TPSP_PARAM_NUM, &status);
paramSetup.option = CSL_EDMA3_OPT_MAKE(CSL_EDMA3_ITCCH_EN, \
CSL_EDMA3_TCCH_DIS, \
CSL_EDMA3_ITCINT_DIS, \
CSL_EDMA3_TCINT_EN, \
TSPS_TCC_CODE, \
CSL_EDMA3_TCC_NORMAL,\
CSL_EDMA3_FIFOWIDTH_NONE, \
CSL_EDMA3_STATIC_DIS, \
CSL_EDMA3_SYNC_AB, \
CSL_EDMA3_ADDRMODE_INCR, \
CSL_EDMA3_ADDRMODE_INCR);
paramSetup.srcAddr = (uint32_t)pSrc;
paramSetup.aCntbCnt = CSL_EDMA3_CNT_MAKE(nUnit, nSrcHeight); // (acnt, bcnt)
paramSetup.dstAddr = (uint32_t)pDst;
paramSetup.srcDstBidx = CSL_EDMA3_BIDX_MAKE(nSrcWidth * nUnit, nUnit); // (src, dst)
paramSetup.linkBcntrld = CSL_EDMA3_LINKBCNTRLD_MAK (CSL_EDMA3_LINK_NULL, 0);
paramSetup.srcDstCidx = CSL_EDMA3_CIDX_MAKE(nUnit, nSrcHeight * nUnit); // (src, dst)
paramSetup.cCnt = nSrcWidth;
CSL_edma3ParamSetup(hParam, &paramSetup);
}

void CTspsManager::ClearInterruptFlag()
{
CSL_edma3ClearLoPendingInterrupts(m_hEdma, TSPS_REGION, 1 << TSPS_TCC_CODE);
CpIntc_clearSysInt(m_nCicId, TSPS_SYSEVT_EDMA3R0);
}

参考资料

  • SPRU187U-TMS320C6000 Optimizing Compiler v7.4
  • SPRUGH7-TMS320C66x DSP CPU and Instruction Set Reference Guide