我用的DSP和TI的TMS320C6678类似,但是是国防科大的6678。它的PCIe外设和TI的有很大不同,但也实现了基本的功能。虽然文档不太详细,而且有些功能有点问题,但基本还是能用的。

链路初始化

  6678的PCIe相关的地址空间是下面这样的,初始化阶段要做的工作就是对本地和远端的PCIe配置空间进行配置。6678作为RC,FPGA端作为EP,FPGA上的PCIe接口相关的寄存器就需要DSP来设置。寄存器的表需要查DSP的文档、FPGA的文档和PCIe协议规范的文档。具体寄存器的功能这里就不细说了。

  链路初始化的第一步是初始化PCIe链路相关的时钟。这个只要按照DSP文档里写的方式初始化就行。
  第二步是设置需要的链路数量和链路速率,链路数量是x4,然后速率是5.0GT/s。

1
2
3
4
5
6
7
8
9
10
#pragma CODE_SECTION (CSL_pcieRcConfig, ".text:csl_section:pcie");
void CSL_pcieRcConfig()
{
CSL_FINST(hPcieRc->LANE_CTL1 , PCIE_LANE_CTL1_LINKCAP, x4);
CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_NUMOFLANE, 4);
CSL_FINS(hPcieRc->LINK_CTL2, PCIE_LINK_CTL2_TGTSPD, 2);// Gen2, 5.0GT/s
CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_DIRECTSPDCHG, 0);
CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_DIRECTSPDCHG, 1);
return;
}

  后面的代码都是参考TI 的CSL库的格式写的。就是每个外设模块都相互独立;对于每个外设的寄存器都定义成一个结构体;每个寄存器里面的每个Field都定义了相应的Mask和Shift;一些默认的参数定义成Token。再有一组宏定义实现了可读性较强的代码风格。比如“CSL_FINS"这个宏定义实现的就是往寄存器的特定Field插入某个值的功能。“CSL_FINST”就是用预先定义好的Token来给特定的Field插入某个值。还有“CSL_FEXT”是提取某个Field 的值,“CSL_FMK”是生成某个寄存器的值。CSL_FINS在用的时候会先读寄存器,再写寄存器;有时候如果不用读或者不能读的话,就要用CSL_FMK来生成某个值直接赋给寄存器。

1
2
3
4
5
6
7
8
#pragma CODE_SECTION (CSL_pcieLinkUp, ".text:csl_section:pcie");
Uint8 CSL_pcieLinkUp()
{
CSL_FINS(hPcieRc->CMD_STATUS, PCIE_CMD_STATUS_LTSSM_EN, 1);
while(CSL_FEXT(hPcieRc->PCIE_CORE_STATUS8, PCIE_PCIE_CORE_STATUS8_LTSSM)!=0x11)
;
return CSL_FEXT(hPcieRc->LINK_STAT_CTL, PCIE_LINK_STAT_CTL_CLINKSPD);
}

  相关链路的配置完成后,就只需要开始链路的初始化,并等待链路建立即可。链路建立后,可以读链路状态寄存器看当前的链路速率是否和设置的一致。
  链路建立之后,还需要分别对RC和EP进一步初始化。DSP有一个InBound和OutBound的地址转换的功能,理论上它可以把在接收TLP包的时候把64bit 的PCIe地址,转换成DSP的32bit的AXI地址;也可以在发送TLP包的时候把DSP的32bit的AXI地址转换成64bit的PCIe地址。但是我试了却发现并没有这个效果,可能是我配置有问题。目前只能用那一段固定的0x61000000~0x6FFFFFFF地址空间。就是我如果往0x6100_0000写数据,在FPGA端就能收到PCIe地址是0x0000_0000_6100_0000的写数据请求,没有地址转换的效果。

  EP端的BAR初始化是必不可少的,结合我的上一篇关于FPGA的PCIe的配置,我需要将上图中的红色基址写入EP端的对应的BAR寄存器中,然后就完成了BAR地址映射,为后面的大批量数据传输做好了准备。初始化过程还有一些东西需要配置,具体有哪些建议参考官方例程。而且寄存器就那么些,配置应该不麻烦,这里就不多说了,不同处理器的配置应该会有不一样。

MSI初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma CODE_SECTION (CSL_pcieMsiInit, ".text:csl_section:pcie");
void CSL_pcieMsiInit(CSL_PcieMsiInfo* pMsgInfo)
{
// ---------------------------- RC MSI Init ----------------------------
hPcieRc->MSI_CTRL_ADDR_LOW = 0x42000000;
hPcieRc->MSI_CTRL_ADDR_HIGH = 0x00000000;
hPcieRc->MSI_CTRL_INT_EN = 0xFFFFFFFF;
hPcieRc->MSI_CTRL_INT_MASK = 0x00000000;
// ---------------------------- EP MSI Init ----------------------------
pMsgInfo->MsgCap = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_MESGCAP);
pMsgInfo->NxtPtr = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_NXT);
pMsgInfo->ID = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_ID);
pMsgInfo->Addr64= CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_ADDR64);
pMsgInfo->Maskable = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_MASKABLE);

hPcieEp->MSI_ADDR_LOW = 0x42000000;
hPcieEp->MSI_ADDR_HIGH = 0x00000000;
hPcieEp->MSI_DATA = 0x00000000;
hPcieEp->MSI_CAP_CTRL_PTR = CSL_FMKT(PCIE_MSI_CAP_CTRL_PTR_MESGNUM, 32) |
CSL_FMK(PCIE_MSI_CAP_CTRL_PTR_MSIEN, 1);
}

  MSI的初始化包括两部分,一部分是对RC的初始化,另一部分是对EP的配置。首先要设置DSP哪个地址用来接收MSI中断,然后打开32个MSi中断使能。EP端,也就是FPGA端,DSP可以读取相应的寄存器,查看它对MSI的支持情况,然后也需要设置同样的MSI地址,MSI个数,并开启使能。理论上来说总共有32个MSI中断,这32个中断向量分别分给8个核,中断向量0、8、16、24,分给核0;中断向量1、9、17、25分给核1,以此类推。但实际上测试发现每个中断向量都会被核0接收,而且产生的都是中断向量0的MSI中断。这个很奇怪,虽然有点影响使用,但是也还好。

PCIe DMA

  DSP的PCIe模块带有DMA功能,而且数据量1B~4GB都可以,虽然只能实现连续的数据块的传输,但优点是使用方便。它有两个DMA读通道,两个DMA写通道。每次只需要给定DMA传输的起始地址、目的地址和字节大小就行。传输完成后会产生传输完成中断,下面是发起DMA读写的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#pragma CODE_SECTION (CSL_pcieDmaReadLocalInt, ".text:csl_section:pcie");
void CSL_pcieDmaWriteLocalInt(
Uint8 chNum,
Uint32 dmaSize,
Uint32 srcAddr,
Uint32 dstAddr
){
CSL_FINS(hPcieRc->DMA_WR_EN, PCIE_DMA_WR_EN, 1);
hPcieRc->DMA_WR_INT_MASK = CSL_FMK(PCIE_DMA_INT_ABORT, 0) |
CSL_FMK(PCIE_DMA_INT_DONE, 0);
hPcieRc->DMA_VIEWPORT_SEL = CSL_FMKT(PCIE_DMA_VIEWPORT_SEL_CHANNEL_DIR, WRITE) |
CSL_FMK(PCIE_DMA_VIEWPORT_SEL_CHANNEL_NUM, chNum);
hPcieRc->DMA_CH_CTRL1 = CSL_FMK(PCIE_DMA_CH_CTRL1_TD, 1) |
CSL_FMK(PCIE_DMA_CH_CTRL1_LIE, 1);
hPcieRc->DMA_SIZE = dmaSize;
hPcieRc->DMA_SRC_LOW = srcAddr;
hPcieRc->DMA_SRC_HIGH = 0x00000000;
hPcieRc->DMA_DST_LOW = dstAddr;
hPcieRc->DMA_DST_HIGH = 0x00000000;
hPcieRc->DMA_WR_DB = CSL_FMK(PCIE_DMA_WR_DB_WR_STOP, 0) |
CSL_FMK(PCIE_DMA_WR_DB_DB_NUM, chNum);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma CODE_SECTION (CSL_pcieDmaReadLocalInt, ".text:csl_section:pcie");
void CSL_pcieDmaReadLocalInt(
Uint8 chNum,
Uint32 dmaSize,
Uint32 srcAddr,
Uint32 dstAddr
)
{
CSL_FINS(hPcieRc->DMA_RD_EN, PCIE_DMA_RD_EN, 1);
hPcieRc->DMA_RD_INT_MASK = CSL_FMK(PCIE_DMA_INT_ABORT, 0) |
CSL_FMK(PCIE_DMA_INT_DONE, 0);
hPcieRc->DMA_VIEWPORT_SEL = CSL_FMKT(PCIE_DMA_VIEWPORT_SEL_CHANNEL_DIR, READ) |
CSL_FMK(PCIE_DMA_VIEWPORT_SEL_CHANNEL_NUM, chNum);
hPcieRc->DMA_CH_CTRL1 = CSL_FMK(PCIE_DMA_CH_CTRL1_TD, 1) |
CSL_FMK(PCIE_DMA_CH_CTRL1_LIE, 1);
hPcieRc->DMA_SIZE = dmaSize;
hPcieRc->DMA_SRC_LOW = srcAddr;
hPcieRc->DMA_SRC_HIGH = 0x00000000;
hPcieRc->DMA_DST_LOW = dstAddr;
hPcieRc->DMA_DST_HIGH = 0x00000000;
hPcieRc->DMA_RD_DB = CSL_FMK(PCIE_DMA_RD_DB_WR_STOP, 0) |
CSL_FMK(PCIE_DMA_RD_DB_DB_NUM, chNum);
}

EDMA

  同样的数据传输,用EDMA也可以完成,而且EDMA更加灵活,可以做不连续的数据块的传输。我用EDMA的QDMA通道0做数据传输,做了如下的配置,使能了QDMA通道0和0号中断。在对应的PaRAM里面将QDMA完成中断指向0号中断。每次我往PaRAM的第7个字写的时候,EDMA1就会产生一个QDMA传输,并在传输完成后产生中断。

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
void edma3QdmaConfig()
{
int i;

CSL_Status status;

CSL_Edma3ModuleAttr edma3Attr;
CSL_Edma3HwSetup setup;
CSL_Edma3HwDmaChannelSetup dmaChannelSetup[64];
CSL_Edma3HwQdmaChannelSetup qdmaChannelSetup[8];

CSL_pscModuleEnable(PSC_MD_DMA1, PSC_PWR_PERI);

hEdma3 = CSL_edma3Open(&edma3Obj, CSL_TPCC_1, &edma3Attr, &status);

for(i = 0; i<64; i++){
dmaChannelSetup[i].paramNum = 0;
dmaChannelSetup[i].que = CSL_EDMA3_QUE_0;
}
for(i = 0; i<8; i++){
qdmaChannelSetup[i].paramNum = 0;
qdmaChannelSetup[i].que = CSL_EDMA3_QUE_0;
qdmaChannelSetup[i].triggerWord = 7;
}

setup.dmaChaSetup = dmaChannelSetup;
setup.qdmaChaSetup = qdmaChannelSetup;
CSL_edma3HwSetup(hEdma3, &setup);

CSL_edma3QDMAChannelEnable(hEdma3, CSL_EDMA3_REGION_GLOBAL, 0);
CSL_edma3InterruptLoEnable(hEdma3, CSL_EDMA3_REGION_GLOBAL, 1<<qdmaIntChNum);// The input arg is a mask
return;
}

  EDMA写PaRAM配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, 1u) |   //PRIV read only 1
CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | //PRIVID read only 0
CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | //Intermediate transfer completion chaining enable
CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | //Transfer complete chaining enable
CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | //Intermediate transfer completion interrupt enable
CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 1) | //Transfer complete interrupt enable
CSL_FMK(TPCC_PARAM_OPT_TCC, qdmaIntChNum) | //Transfer complete code
CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | //Transfer complete code mode; 0 - Normal, 1 - Early completion
CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | //FIFO width in CONST mode; 8/16/32/64/128/256
CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | //Static set; 0 - Set is not static, 1 - Set is static
CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | //Transfer synchronization dimension; 0 - A mode; 1 - AB mode
CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | //Destination address mode 0-INCR; 1-CONST
CSL_FMK(TPCC_PARAM_OPT_SAM, 0); //Source address mode 0-INCR; 1-CONST
paramSetup.srcAddr = (Uint32)txData;
paramSetup.aCntbCnt = 0x02000280;//aCnt = 640, bCnt = 512
paramSetup.dstAddr = 0x68000000u;
paramSetup.srcDstBidx = 0x02800280;
paramSetup.linkBcntrld = 0x0280FFFF;
paramSetup.srcDstCidx = 0x00000000;
paramSetup.cCnt = 0x00000001;

  EDMA读PaRAM配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, 1u) |   //PRIV read only 1
CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | //PRIVID read only 0
CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | //Intermediate transfer completion chaining enable
CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | //Transfer complete chaining enable
CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | //Intermediate transfer completion interrupt enable
CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 1) | //Transfer complete interrupt enable
CSL_FMK(TPCC_PARAM_OPT_TCC, qdmaIntChNum) | //Transfer complete code
CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | //Transfer complete code mode; 0 - Normal, 1 - Early completion
CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | //FIFO width in CONST mode; 8/16/32/64/128/256
CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | //Static set; 0 - Set is not static, 1 - Set is static
CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | //Transfer synchronization dimension; 0 - A mode; 1 - AB mode
CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | //Destination address mode 0-INCR; 1-CONST
CSL_FMK(TPCC_PARAM_OPT_SAM, 0); //Source address mode 0-INCR; 1-CONST
paramSetup.srcAddr = 0x68000000;
paramSetup.aCntbCnt = 0x02000280;//aCnt = 640, bCnt = 512
paramSetup.dstAddr = (Uint32)rxData;
paramSetup.srcDstBidx = 0x02800280;
paramSetup.linkBcntrld = 0x0280FFFF;
paramSetup.srcDstCidx = 0x00000000;
paramSetup.cCnt = 0x00000001;

  在用EDMA传输的时候,遇到了一个问题,就是如果把传输模式改成A-mode,她就不能正常完成读写了。只有把把PaRAM里OPT中的SYNDIM(Transfer synchronization dimension)改成1才能正常读写。

程序计时

  后面准备对DMA传输的速度做个测试,所以需要比较准确的计时方式。之前在做SRIO速率测试的时候有用到过,就是用TSC(Time Stamp Counter Registers)寄存器。头文件需要调用<c6x.h>,然后需要两个无符号的long long 类型的变量来存TSCL和TSCH的值。给TSCL赋0来启动TSC。后面用_itoll()将两个32位寄存器拼成64bit的long long类型来获取当前计数值。将tStart和tStop作差即可求出总的时间开销,这一计数值的单位为系统时钟的一个周期,比如我的系统时钟是1GHz,那么这个计数值就是ns为单位。

1
2
3
4
5
6
7
#include <c6x.h>
unsigned long long tStart;
unsigned long long tStop;
TSCL= 0; // Start TSC
tStart= _itoll(TSCH, TSCL);
......
tStop = _itoll(TSCH, TSCL);