ADSP21569\21593 FIRA驱动与调试


前言

最近在使用ADSP21593进行开发,相较于之前的21569开发,双核的系统也给调试带来了一些问题。所以将开发调试的过程结果记录下来,用于分享和保存。当然所有未在文档里有明确解释的内容都是通过自己理解,欢迎指正与批评。


ADSP

21593是ASDP系列的一款数字处理器,属于SC594家族,由2个SHARC+核心与其外设组成。其外设具备音频接口,支持I2S和TDM等数据协议,并带有硬件FIR加速器,用于高算力需求的音频应用。
ADSP-SC594框图(去掉core0即为ADSP21593)
ADSP-SC594框图(去掉core0即为ADSP21593)
21593有两个SHARC+核心和连个FIRA加速器,所以理论上的算力是21569的两倍,但是外设资源是共享的。

FIRA

FIRA驱动模式
在这里插入图片描述

FIRA是ADI为该处理器设计的硬件FIR滤波器算法的加速器,此外还有IIR加速器和FFT加速器。在ADSP2159x的hardware reference下的2842页有FIRA的驱动模型,与需要写的代码是相关的。简单来说分为以下几个步骤

  1. 设置FIR_CTL1寄存器的CH位,表示几个通道(通道数在这里的意思是有几次FIR的运算)
  2. 在内存中配置一个FIRA的控制块(TCB)。Transfer control block是ADSP芯片中的一个概念,实际上就是一个数组,里面配置了有关DMA的传输信息。在ADSP中各种外设都使用DMA传输,而非CPU能直接读取。legacy模式的FIRA TCB如图,其中需要配置的是FIR需要的各种数据。FIRA TCB
  3. 将FIRA的数据卸任FIR_CHNPTR寄存器,并且使能FIRA。之后加速器便会根据TCB中的信息使用DMA将输入数组/系数数组传输至FIRA自带的RAM内,并对其进行计算。计算结果也同样将会有DMA从内部RAM传输至系统RAM。在例程中,使用FIRA的方式有两种:驱动库API以及直接配置TCB并赋值寄存器。

TCB

  • FIR_CHNPTR:下一个TCB的地址(相当于链表),如果有不止一个通道需要进行FIR运算,可以在配置好多个通道后进行一次性使能,在所有的通道计算完毕后才会停止。
  • FIR_COEFCNT: 系数的数量
  • FIR_COEFMOD:系数的偏置,即隔几个数据取用于计算的数据。例如这里偏移数量是2的话,第1、3、5。。。个数据将用于计算。
  • FIR_COEFIDX:系数的指示,系数的首地址
  • FIR_OUTBASE:预分配的计算结果数组的首地址
  • FIR_OUTCNT:计算结果的数量
  • FIR_OUTMOD:计算结果的偏移数量
  • FIR_OUTIDX:计算结果的指示,填写的还是计算结果的数组的首地址,不明白用意也没找到解释
  • FIR_INBASE:输入数据的首地址
  • FIR_INCNT:输入数据的数量
  • FIR_INMOD:输入数据的偏移数量
  • FIR_INIDX:输入数据的指示
  • FIR_CTL2:CTL2寄存器的值

驱动库API

代码如下:

	//以下代码均来源于FIR_Processing_21593_Core1,属于ADI_EV-SC59x_EZ-KIT支持包里的例程,可以在官网下载
ADI_FIR_CHANNEL_INFO FirTask2_Channels[FIR_NUMBER_OF_CHANNELS_TASK2] = {

			{
				TAPS2,                                                      /* Tap Length */
				FIR_WINDOW_SIZE,                                            /* Window Size */
				ADI_FIR_SAMPLING_SINGLE_RATE,                               /* Sampling */
				1,                                                          /* Sampling Ratio */
#if  ADI_FIR_CFG_ACCELERATOR_MODE == ADI_FIR_ACCELERATOR_MODE_ACM
				true,                                                       /* Callback Enable */
				false,                                                      /* Generate Trigger on completion */
				false,                                                      /* Wait for Trigger */
				ADI_FIR_FLOAT_ROUNDING_MODE_IEEE_ROUND_TO_NEAREST_EVEN,     /* Rounding Mode */
				false,                                                      /* Fixed point enable */
				ADI_FIR_FIXED_INPUT_FORMAT_UNSIGNED_INTEGER,                /* Fixed Point format */
#endif /* MODE == ACM */
				TAPS2,                                                      /* Coefficient Count */
				1,                                                          /* Coefficient Modify */
				(void *)CoeffBuff3,                                         /* Coefficient Index */
		        (void *)FirOutputBuff3,                                     /* Output Base */
				FIR_WINDOW_SIZE,                                            /* Output Count */
				1,                                                          /* Output Modify */
				(void *)FirOutputBuff3,                                     /* Output Index */
		        (void *)FirInputBuff3,                                      /* Input Base */
				TAPS2+FIR_WINDOW_SIZE-1,                                    /* Input Count */
				1,                                                          /* Input Modify */
				(void *)FirInputBuff3                                       /* Input Count */

			},
			{
				TAPS2,                                                      /* Tap Length */
				FIR_WINDOW_SIZE,                                            /* Window Size */
				ADI_FIR_SAMPLING_SINGLE_RATE,                               /* Sampling */
				1,                                                          /* Sampling Ratio */
#if  ADI_FIR_CFG_ACCELERATOR_MODE == ADI_FIR_ACCELERATOR_MODE_ACM
				true,                                                       /* Callback Enable */
				false,                                                      /* Generate Trigger on completion */
				false,                                                      /* Wait for Trigger */
				ADI_FIR_FLOAT_ROUNDING_MODE_IEEE_ROUND_TO_NEAREST_EVEN,     /* Rounding Mode */
				false,                                                      /* Fixed point enable */
				ADI_FIR_FIXED_INPUT_FORMAT_UNSIGNED_INTEGER,                /* Fixed Point format */
#endif /* MODE == ACM */
				TAPS2,                                                      /* Coefficient Count */
				1,                                                          /* Coefficient Modify */
				(void *)CoeffBuff4,                                         /* Coefficient Index */
		        (void *)FirOutputBuff4,                                     /* Output Base */
				FIR_WINDOW_SIZE,                                            /* Output Count */
				1,                                                          /* Output Modify */
				(void *)FirOutputBuff4,                                     /* Output Index */
		        (void *)FirInputBuff4,                                      /* Input Base */
				TAPS2+FIR_WINDOW_SIZE-1,                                    /* Input Count */
				1,                                                          /* Input Modify */
				(void *)FirInputBuff4                                       /* Input Count */

			}

	};
	/* Open the FIR Device */
    eFirResult  = adi_fir_Open(0u,&hFir);
    CHECK_FIR_RESULT(eFirResult)

    /* Register the Callback */
    eFirResult = adi_fir_RegisterCallback(hFir,FIRTaskDoneCallback,0);
    CHECK_FIR_RESULT(eFirResult)

    /* Create Accelerator Tasks */
    eFirResult = adi_fir_CreateTask(hFir, /* Device Handle*/
    		FirTask1_Channels,            /* Pointer to Channel List */
			FIR_NUMBER_OF_CHANNELS_TASK1, /* Number of Channels  */
			&FirTask1Memory,              /* Pointer to Memory */
			FIR_MEM_SIZE_TASK1,           /*Memory Size */
			&hFirTasks[0]);               /* Address to store the handle */
    CHECK_FIR_RESULT(eFirResult)

    eFirResult = adi_fir_CreateTask(hFir, /* Device Handle*/
    		FirTask2_Channels,            /* Pointer to Channel List */
			FIR_NUMBER_OF_CHANNELS_TASK2, /* Number of Channels  */
			&FirTask2Memory,              /* Pointer to Memory */
			FIR_MEM_SIZE_TASK2,           /*Memory Size */
			&hFirTasks[1]);               /* Address to store the handle */
    CHECK_FIR_RESULT(eFirResult)

    /* Queue the Tasks Created */
    for(count = 0; count<FIR_NUMBER_OF_TASKS; count++)
    {
        eFirResult = adi_fir_QueueTask(hFirTasks[count]);
        CHECK_FIR_RESULT(eFirResult)
    }

具体步骤和ADI的application note EE436中的方式一样 ,该软件驱动库以任务为单位,并且将任务加入一个使用FIRA的队列中,并读取FIR DMA Status Register的值进行状态查询,判断该任务的运算是否完毕等等。有兴趣的可以查阅API中的源码(21569与21593使用的同一套API,其中区别是有没有core2)
FIRA驱动库

直接写入寄存器

代码如下(示例):

//代码来源是FIRA_Throughput_21593_Core1,属于ADI_EV-SC59x_EZ-KIT支持包里的例程,可以在官网下载
void FIRA_Init(uint32_t uiFIR_GCTL, uint32_t* uiFIR_CHNPTR)
{
	*pREG_FIR0_CHNPTR=(uint32_t)uiFIR_CHNPTR;
	clock_start=clock();
	*pREG_FIR0_CTL1=uiFIR_GCTL;
}

uint32_t i = 0;
int main(int argc, char *argv[])
{

	uint32_t w; /* Window */;
	uint32_t t; /* Taps */
	/**
	 * Initialize managed drivers and/or services that have been added to 
	 * the project.
	 * @return zero on success 
	 */
	adi_initComponents();
	
	FILE * fpout;

	fpout = fopen("Results(Cycles).csv","w+");

	for(w=1;w<=MAX_WINDOW_SIZE;w++)
	{
		for(t=1;t<=MAX_TAP_LENGTH;t++)
		{
			//Initialize FIR TCBs for both the channels
			FIRA_TCB[0]=0;//((int)(FIRA_TCB2+12)>>2)|MP_OFFSET;	//CP
			FIRA_TCB[1]=t;	//CL
			FIRA_TCB[2]=1;	//CM
			FIRA_TCB[3]=((int)CoeffBuff>>2)|MP_OFFSET;	//CI
			FIRA_TCB[4]=((int)OutputBuff>>2)|MP_OFFSET;	//OB
			FIRA_TCB[5]=w;	//OL
			FIRA_TCB[6]=1;	//OM
			FIRA_TCB[7]=((int)OutputBuff>>2)|MP_OFFSET;	//OI
			FIRA_TCB[8]=((int)InputBuff>>2)|MP_OFFSET;	//IB
			FIRA_TCB[9]=t+w-1;	//IL
			FIRA_TCB[10]=1;	//IM
			FIRA_TCB[11]=((int)InputBuff>>2)|MP_OFFSET;	//II
			FIRA_TCB[12]=(t-1)|(w-1)<<BITP_FIR_CTL2_WINDOW;	//FIRCTL2

			//Disable accelerator
			*pREG_FIR0_CTL1 &= ~(BITM_FIR_CTL1_EN);

			//Start the IIR accelerator
			FIRA_Init(BITM_FIR_CTL1_EN|BITM_FIR_CTL1_DMAEN|(CHANNEL_NO-1)<<BITP_FIR_CTL1_CH|BITM_FIR_CTL1_BURSTEN|BITM_FIR_CTL1_PFB_EN,(uint32_t *)(((int)(FIRA_TCB+12)>>2)|MP_OFFSET));

			//wait till all channels are done
			while((*pREG_FIR0_DMASTAT & BITM_FIR_DMASTAT_ACDONE) == 0x00000000);

			clock_stop=clock();
			cycles=clock_stop-clock_start;

			//printf("%d\n",cycles);
			fprintf(fpout,"%d,",cycles);
	    }

		fprintf(fpout,"\n");
	}
	fclose(fpout);
    return 0;
}

这种方式的配置更为简洁,先配置好TCB的值,最后是配置好FIRA加速器的寄存器后后再进行使能。为了避免出现加速器在运行中被中断的情况,加入while((*pREG_FIR0_DMASTAT & BITM_FIR_DMASTAT_ACDONE) == 0x00000000);使在DMA传输完毕标志位提示完成后再执行 之后的代码。


双核驱动与性能优化

在开发过程中,作者也发现了一个很奇怪的问题:
作为一个双核的处理器,似乎没有例程用于解释双核是如何驱动第二个FIRA的。
所以作者就只能从仅有的hardware reference文档和API中寻找如何作双核驱动的方案。首先想到的是既然有两个FIRA硬件加速器,那应该同样具备两组FIRA的寄存器。
FIRA寄存器
果然,在文档中搜索了以下,确实是有两组,值得吐槽的是在这个FIR的寄存器描述中既有FIR寄存器又有FIR0寄存器还有FIR1寄存器,并且FIR1寄存器和FIR寄存器的地址完全一样。可能是校对错误还是沿用了之前的文档后没有更改还是有一些我目前还不能理解的意图。

第一次尝试
在发现寄存器存在差异之后,便是将上述直接写寄存器的驱动方法中的所有FIR0替换成了FIR1,例如pREG_FIR0_CTL1改为pREG_FIR1_CTL1
运行的结果是驱动不成功

printf("1 soft %.8f %d FIRA %.8f %d \n", conv_result,clock_3-clock_2, FIRA.Output_Buffer,clock_2-clock_1);

这行代码被我用于在调试时查看计算结果,确保软件型的卷积结果结果与FIRA的计算结果一致,但在这次尝试种FIRA的计算结果始终为0。
第二次尝试
第一次尝试不通过之后,于是便想试试如果调用ADI驱动库API的方式是否可行。
FIRA驱动库
在工程下的system.svc(1)中可以添加FIR driver(2)到工程里,完成后会有3中的两个文件。因为以前踩过在头文件里的静态配置没有更改的坑,所以这次先查看了下adi_fir_config_SC59x.h里是否有需要使能的宏定义。
在这里插入图片描述
于是在文件中发现了有关于双SHARC核有关于此的驱动,并将红框中的内容替换为了ADI_FIR_PROCESSING_CORE_SELECT_BOTH_SHARCS。并且从注释中得知,在ADI的设计当中每一个sharc核心应该只驱动一个FIRA加速器,例如图中配置为核心1驱动FIRA0,核心2驱动FIRA1。
在这里插入图片描述
并且在这个adi_fir_Open的函数的参数中,第一项为设备的设备号,那现在根据头文件中的配置,核心2的工程中该参数应该填写1。
检验结果
并且在源码中,对于调用该加速器的是否是于头文件中核心是否是对应核心进行了检验。

在经过测试后,该方法对于两个核同时分别驱动两个FIRA是可行的,结论通过与软件型卷积的计算结果相比较,差异大概偶尔会有0.0000005(截图没留),可能是浮点数在计算式的近似造成的
但是与之前的通过寄存器直接写入调用的方式相比速度过慢(截图同样没留哈哈),使用clock函数得到system tick进行比较的话,200阶的卷积软件型是4400个tick左右,以库驱动的方式大概在2200个tick左右,而直接写入时花费130个tick,性能差距较大

第三次尝试
为了提高系统的性能,在这之后也做了一些尝试,例如减少函数的重复调用等情况,最少是将tick减少
至了500多(因为驱动库中有不少检查输入参数是否有效的代码,因此运气起来效率较差,当然这都是建立在编译参数为debug版本的情况下,忘记尝试reliease的情况下性能如何了),感觉还是不太满意。于是最终的目标就是让核2能用直接写入寄存器的方法调用FIRA。想到的方法就是审查驱动库 中的源码,看看到底有哪些操作,与program mode中的区别到底在哪。
最终在一番努力之后发现驱动库除了task的部分,其余部分与program mode中的流程是一致的,除了函数。
create task

queue task
在create和queue task的过程中,所有有关地址的配置项,全都调用了TranslateAddr这个函数,而这个函数调用了adi_rtl_internal_to_system_addr这个函数,并将所有的转换得到的地址右移了两位。右移两位是作者之前思考过的问题,但并没有在文档里找到解释,所以以下都是猜想。FIRA的标准输入是IEEE的单精度浮点类型,即float是4个字节的长度,而对于215xx以后的系统地址来说,每一个地址的位宽是1个字节,但这个FIRA硬件加速器是在应用在215xx之前就已经设计好了,两者之间并不是完全匹配的。
例如21489的每一个地址的位宽是4字节,所以对于21489的驱动来说,在填写地址的时候没有进行地址的转换,并且在modifier的配置上填写1,是可以完整的传输4字节的数据,并且移位一个地址的时候能指向下一个数据,没有问题。而在215xx以上的芯片,不做右移两位的操作,并且仍在modifier处写1的话,地址指向的不是下一个float的首地址,而是第一个float的第二个字节,因此会造成错误,所以21593的驱动中,给FIRA指示数据的地址需要右移两位,这样对于modifier每右移一位实际上是在内存上移动了四位,便对应了4个字节。
转换
那问题就在于这个转换地址的函数具体作用是什么?
因为不能直接在ide中跳转到c文件,于是又经过一番努力在IDE的安装目录下CCES\SHARC\lib\src\libcc_src文件夹中找到了包含这个源码的c文件translate_internal_to_system_addr.c,其中函数如下
translate_internal_to_system_addr
可以看到对于核1需要将地址对SHARC1_L1_SLAVE1_OFFSET做一个偏置
于是方法很简单了,将所有有关地址的操作,同样调用了这个函数,再进行驱动的尝试
(仍然没存图)调试最终的结果是直接写入的方法将所有地址通过这个函数转换后FIRA的驱动时成功了,但仍需要耗费300多个tick,与100多个tick仍然有差距。再随便一思索,这函数嵌套了两个函数,肯定是比直接运算要慢得多了,所以直接把这个函数确定的偏移再移位操作写成了一个宏函数,测试后得到了喜闻乐见的结果计算结果与软件卷积的计算结果相差无几,而消耗时间也与core1的130多个tick一毛一样,可以说是调试成功了

后言

虽然调试成功了,但是还有一个问题没有解决,即为什么需要对最后的地址使用TranslateAddr函数做地址转换。
再次审视上述函数过后,可以发现对于core1和21569芯片来说,其需要的偏置都为SHARC0_L1_SLAVE1_OFFSET
地址偏置
可以看到SHARC0_L1_SLAVE1_OFFSET的值和SHARC1_L1_SLAVE1_OFFSET的值
与这个偏置不同的是,在core1/21569中直接写入时,对地址的处理操作是

#define MP_OFFSET 0xA000000
FIRA_TCB[3]=((int)CoeffBuff>>2)|MP_OFFSET

当时通过例程调通了程序之后,并没有想通这个操作的意图,而且MP_OFFSET也没有给出任何的注释,于是就得过且过了。但是看到adi_rtl_internal_to_system_addr这个函数之后才开始注意到当时是先进行了移位操作,再进行了操作。如果先进行或操作再进行移位的话,MP_OFFSET的值就应当变为
在这里插入图片描述
0x28000000即为SHARC0_L1_SLAVE1_OFFSET,所以如果该地址本来前六位都是0的话,或操作与偏移操作是等价的。
那为什么要进行地址的偏移呢?带着这个疑惑肯定是要看一看memory map的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
顺着memory map中红框的发现了这些东西,于是猜测如下
第一张图右下角的红框说明了可能对于一个处理器来说,他的L1内存应该私有的(实际上L1cache是离处理器最近的内存了,一般是有L1 L2 L3内存和普通的ram),并且应该对于每一个核心来说,他都是在0x00240000-0x0039FFFF之间的,每一个核心在系统中都管理自己的区域(类似于MMP?)。但是对于外设来说,一个外设服务哪个核心都是有可能的,比如说DMA,中断等等,这就要求每一个核心的内存在整个系统上还是有一个确定的地址。ADI的解决办法就是通过requester核completer把core1的L1内存地址定于0x28240000-0x28400000,core2则位于0x28A40000-0x28BA0000。而在给外设DMA配置表明自己的地址的时候,就需要加上上述偏置,使对于核心的地址转换成对于系统的地址。

至于requester和completer是什么?我对此并不了解,在网上查找时觉得最相近的应该是PCIe的总线机制,对于这个并不了解,就不多展开了。88

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值