今回は、KinetisマイコンのDMA転送は、どのくらいの転送スピード(スループット)があるのか気になったので、測定してみました。
DMA転送は、RAM-TO-RAM転送、ROM-TO-RAM転送の2種類です。Kinetisマイコンでは、DMAのRAMアクセスは、ノーウェイトですが、ROM(FlashROM)アクセスの場合はウェイトが入ります。
RAMや ROMへの転送でスループットはどの程度変化があるでしょうか。
コンテンツ
やりたいこと
やりたいことは、シンプルです。DMA転送で、RAMからRAMへ転送する場合と、FlashROMからRAMへ転送する場合のスループットを測定することです。
FlashROMへのアクセスと、RAMへのアクセススピードがどの程度異なるのか見てみます。
使用したボード
FRDM-K64F
ソースコード
ソースコードは、gitHubからダウンロード可能なので、是非試してみてください。
GitHub : https://github.com/mcuthings/dma-throughput
/*******************************************************************************
* Definitions
******************************************************************************/
//#define TRANSFER_ROM_TO_RAM
#define TRANSFER_RAM_TO_RAM
#define EXAMPLE_DMA DMA0
#define EXAMPLE_DMAMUX DMAMUX0
#define DMA_CH0 0
#define DMA_CH1 1
#define DMA_CH2 2
#define DMA_CH3 3
#define TRANSFER_MODE
#define BUFF_LENGTH 8192UL
/* Systick Counter */
#define GET_TICKS() (0xFFFFFF - SysTick->VAL)
#define STOP_COUNTING() (SysTick->CTRL &= (~SysTick_CTRL_ENABLE_Msk))
/*******************************************************************************
* Prototypes
******************************************************************************/
void SysTick_Handler(void);
void dmaTransfer(void* srcAddr, void* destAddr);
/*******************************************************************************
* Variables
******************************************************************************/
/*DMA Variables*/
edma_handle_t* g_EDMA_Handle[4];
edma_handle_t g_EDMA_Handle_ch0; //for the test of 1byte(8bits) transfer
edma_handle_t g_EDMA_Handle_ch1; //for the test of 4byte(32bits) transfer
edma_handle_t g_EDMA_Handle_ch2; //for the test of 16byte transfer
edma_handle_t g_EDMA_Handle_ch3; //for the test of 32byte transfer
static edma_transfer_config_t transferConfig;
static edma_config_t userConfig;
static uint32_t dma_start,dma_end;
static void* srcAddr; //Source addr for DMA
extern char _binary_randomData_bin_start[]; //Source addr in case of ROM-to-RAM transfer
static uint32_t SysIsrcount=0;
__attribute__ ((section (".data.$SRAM_LOWER") )) volatile bool g_Transfer_Done = false;
__attribute__ ((section(".data.SRAM_UPPER"))) __attribute__ ((aligned(32))) uint8_t srcRAM[BUFF_LENGTH]={0}; //source table array for RAM-to-RAM
__attribute__ ((section(".data.SRAM_UPPER"))) __attribute__ ((aligned(32))) uint8_t destRAM[BUFF_LENGTH] = {0};
/*******************************************************************************
* Code
******************************************************************************/
/* User callback function for EDMA transfer. */
void EDMA_Callback(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
dma_end = GET_TICKS();
STOP_COUNTING();
if (transferDone)
{
g_Transfer_Done = true;
}
}
void SysTick_Handler(void){
SysIsrcount++;
}
__attribute__ ((long_call, section (".ramfunc.$SRAM_LOWER") )) void dma_polling(void)
{
/* Wait for EDMA transfer finish */
while (g_Transfer_Done != true)
{
}
}
/*!
* @brief Main function
*/
int main(void)
{
uint32_t i = 0;
uint32_t srcRamTemp[BUFF_LENGTH];
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
/* Print source buffer */
PRINTF("EDMA memory to memory transfer example begin.\r\n\r\n");
/* For RAM-to-RAM transfer test
* Prepare source data */
for (uint32_t i = 0; i < BUFF_LENGTH; i++)
{
srcRAM[i] = i%256; //mod(256) to store a byte
}
/* Configure DMAMUX */
DMAMUX_Init(EXAMPLE_DMAMUX);
#if defined(FSL_FEATURE_DMAMUX_HAS_A_ON) && FSL_FEATURE_DMAMUX_HAS_A_ON
DMAMUX_EnableAlwaysOn(EXAMPLE_DMAMUX, 0, true);
#else
for (uint8_t i=0; i<4;i++){
DMAMUX_SetSource(EXAMPLE_DMAMUX, i, 63);
}
#endif /* FSL_FEATURE_DMAMUX_HAS_A_ON */
for (uint8_t i=0; i<4;i++){
DMAMUX_EnableChannel(EXAMPLE_DMAMUX, i);
}
/* Configure EDMA one shot transfer */
/*
* userConfig.enableRoundRobinArbitration = false;
* userConfig.enableHaltOnError = true;
* userConfig.enableContinuousLinkMode = false;
* userConfig.enableDebugMode = false;
*/
EDMA_GetDefaultConfig(&userConfig);
EDMA_Init(EXAMPLE_DMA, &userConfig);
/*DMA handle creation*/
EDMA_CreateHandle(&g_EDMA_Handle_ch0, EXAMPLE_DMA, DMA_CH0);
EDMA_CreateHandle(&g_EDMA_Handle_ch1, EXAMPLE_DMA, DMA_CH1);
EDMA_CreateHandle(&g_EDMA_Handle_ch2, EXAMPLE_DMA, DMA_CH2);
EDMA_CreateHandle(&g_EDMA_Handle_ch3, EXAMPLE_DMA, DMA_CH3);
g_EDMA_Handle[0] = &g_EDMA_Handle_ch0;
g_EDMA_Handle[1] = &g_EDMA_Handle_ch1;
g_EDMA_Handle[2] = &g_EDMA_Handle_ch2;
g_EDMA_Handle[3] = &g_EDMA_Handle_ch3;
/* DMA Transfer throughput test */
PRINTF("\r\nDMA Transfer from RAM to RAM\r\n");
dmaTransfer(srcRAM, destRAM);
PRINTF("\r\nDMA Transfer from ROM to RAM\r\n");
dmaTransfer(_binary_randomData_bin_start, destRAM);
PRINTF("\r\n\r\nEDMA memory to memory transfer example finish.\r\n\r\n");
while (1)
{
}
}
void dmaTransfer(void* srcAddr, void* destAddr){
/* DMA Throughput counter */
uint32_t transferByte;
volatile uint32_t cnt;
volatile uint32_t ret;
double coreClock;
double scalingFactor;
uint32_t result;
for (uint8_t i=0; i<4;i++){ //loop while ch0 - ch3
EDMA_SetCallback(g_EDMA_Handle[i],
EDMA_Callback, NULL);
switch (i){
case 0: transferByte = (uint32_t) 1; //1Byte transfer
break;
case 1: transferByte = (uint32_t) 4; //4Byte transfer
break;
case 2: transferByte = (uint32_t) 16; //16Byte transfer
break;
case 3: transferByte = (uint32_t) 32; //32Byte transfer
break;
default:
break;
}
EDMA_PrepareTransfer(&transferConfig, srcAddr, transferByte, destAddr, transferByte, (uint32_t)(BUFF_LENGTH), (uint32_t)(BUFF_LENGTH*512), kEDMA_MemoryToMemory);
EDMA_SubmitTransfer(g_EDMA_Handle[i], &transferConfig); EDMA_SetModulo(EXAMPLE_DMA, g_EDMA_Handle[i]->channel, kEDMA_Modulo8Kbytes, kEDMA_Modulo8Kbytes);
ret= SysTick_Config(0xFFFFFF);/*<---- Here starts the cycle count */
if(ret){
PRINTF("SysTick configuration is failed.\n\r");
while(1);
}
dma_start = GET_TICKS();
g_Transfer_Done = false;
SysIsrcount = 0;
EDMA_StartTransfer(g_EDMA_Handle[i]);
dma_polling(); //Polling the DMA transfer complete status
cnt = dma_end - dma_start;
cnt += (SysIsrcount*0xFFFFFF);
coreClock = CLOCK_GetCoreSysClkFreq();
scalingFactor = (double)cnt/coreClock;
result=(BUFF_LENGTH*512/scalingFactor)/(1024*1024);//Unit is [MB/Sec]
/* Print out result */
PRINTF("DMA throughput (Transfer size %dByte) is %d MB/Sec\r\n",transferByte ,result);
}
}
コアクロック、システムクロック
コア、およびシステムのクロックは、100MHzとしています。もし、ウェイトがなければ、転送スピードは、200MB/Secが出ることになります。
転送するデータは、配列として8KB用意しています。転送元をRAMとROMに配置しています。変数のRAM配置は、以前の記事を参考にしてください。
参考:GCCで任意の関数(ramfunc)をRAM実行するには???
KInetisのSRAMは、LOWERとUPPERの2バンクで構成されていて、スループットをあげるには、DMAはUPPER側、コアはLOWER側にアクセスすると、SRAMに同時アクセスが可能なデュアルポート仕様になっています。
転送サイズは、BUF_LENGTH(8KB) x 512=4MB
DMAコンフィグ
基本は、SDKのデフォルトの設定のままです。DMAの転送元(Source)および転送先(Destination)は、8KBのモジューロ設定にしてあります。
4MBものRAM領域はないので、8KBのモジューロで繰り返し転送している感じですね。
EDMA_Set_Callback():
メジャーループカウントが完了すると、設定したコールバック関数を呼んでくれます。
EDMA_PrepareTransfer():
DMAの転送元や転送先、転送サイズや転送合計バイトなどを設定する関数です。
EDMA_SubmitTransfer():
設定した転送情報をTCDに設定してくれます。
EDMA_SetModulo():
8KBモジューロの設定はここでしています。
EDMA_StartTransfer():
最後にDMAを走らせます。
DMA転送完了をポーリング
DMA転送が完了した時に割り込みが発生する設定になっています。DMA転送の完了フラグをコアがポーリング(dma_polling()関数)しますが、この関数もコアはLOWER側にアクセスするようにしています。
コアのアクセスはSRAM_LOWER、DMAアクセスはSRAM_UPPERということですね。
転送時間の計測
転送時間の計測には、今回はSystickタイマーを使用しました。Systickタイマーによる計測はとっても簡単です。
以前の記事で詳しく説明していますので、参考にしてください。
参考:CMSISで簡単!サイクル数を計測する方法
24ビットカウンターで測定できる時間以上の転送時間が掛かると、カウンタが足りずに0になってしまい、正しく計測できません。 そこで、次のように今回はSystickタイマーのカウンター0で割り込みが発生したら、上記の割り込みコールバック内で何回カウンター0になったかカウント(SysIsrcount)します。
void SysTick_Handler(void){
SysIsrcount++;
}
計測の集計時に24ビットカウンターが回った回数分を追加して計測しています。
最後にDMA転送完了時のカウントから開始時のカウントを引いて、Systickcount(Systickタイマーが何周したか)を加味します。
あとは、カウントをクロックで割ると処理時間になります。
cnt = dma_end - dma_start;
cnt += (SysIsrcount*0xFFFFFF);
coreClock = CLOCK_GetCoreSysClkFreq();
scalingFactor = (double)cnt/coreClock;
result=(BUFF_LENGTH*512/scalingFactor)/(1024*1024);//Unit is [MB/Sec]
計測結果

RAM-TO-RAM転送の場合には、転送サイズ32バイトで、169MB/Secという結果になりました。レファレンスマニュアルに記載されている値より低いですね〜。
DMAからRAMにRead・Writeすると、ウェイトが発生するのだと思います。
一方、ROM-TO-RAM転送の場合には、152MB/Secとなり、転送サイズが16バイトから大きくしても、152MB/Secとなり、この転送スピードがピークのようです。DMAによるROMへのアクセスは、さらにウェイトが発生していることになります。
まとめ
今回は、Kinetis K64のDMA転送のスループットを測定してみました。積極的にDMAを使用してシステムのパフォーマンスを向上することが出来ます。
レファレンスマニュアル通りのピーク転送速度は計測出来ませんでしたが、KinetisのDMAは、非常に柔軟で高機能な動作が設定可能です。是非、積極的に使いたいものです。