在线时间4804 小时
UID3441752
注册时间2017-11-21
NXP金币76805
TA的每日心情 | 开心 2025-7-11 08:53 |
---|
签到天数: 301 天 连续签到: 2 天 [LV.8]以坛为家I
管理员
  
- 积分
- 40090
- 最后登录
- 2025-8-29
|
1 简介
本应用笔记的背景是在对 i.MX RT1180 中的 NETC 模块进行性能测试时,现有的 SDK 所提供的函数接口不能测试出 NETC 的最佳性能数据。所以为了排除软件层面的影响,需要对函数做出一些改造以测试出 NETC 模块的最佳性能。
i.MX RT1180 是 i.MX RT 系列新推出的一款芯片,作为一款 MCU 芯片,i.MX RT1180 首次搭载了 NETC 模块。NETC 是一个包含 TSN Switch 以及普通以太网控制器功能的模块,此模块中的 TSN Switch 引出了 4 个 1G 物理接口,并且内部还包含了一个虚拟接口,最高能够提供 5 Gbps 的数据转发能力。
本篇应用笔记主要介绍了如何优化现有的收发函数配合 Cortex-M7 核来进行性能测试以获取 NETC 的最佳性能。
2 NETC 模块简介
NETC是一个具有TSN能力的以太网IP,可实现全面的以太网解决方案组合。NETC提供的主要功能之一是对时间敏感网络(TSN)的支持。TSN是IEEE制定的一组标准,它在传统的尽力而为传输的以太网内增加了对时间敏感流量的支持。TSN允许时间敏感的流量和尽力而为流量在同一网络上共存,使得以太网实现了有界限的低时延而具备了确定性,并提高了以太网的可靠性和弹性。
图 1 显示了 NETC 模块的功能框图。NETC 提供了很全面的以太网功能,包含以太网控制器以及交换机的功能。
RT1180 中的 NETC 模块实现了一个包含 5 个端口的以太网交换机,以及两个的以太网控制器。其中一个以太网控制器通过内部的一个虚拟 MAC 与交换机的端口 4 相连。从芯片外部看,交换机部分有 4 个物理端口引出,除了连接交换机的四个口以外还有一个单独引出的以太网端口 Eth4。
3 测试环境配置
NETC performance 的测试主要测试了 Switch 的转发性能以及 Endpoint 端有 CPU 参与的数据传输性能。完整的测试项目以及测试配置可以参考《基于思博伦的 RT1180 的 NETC 性能测试》(文档 AN13827)。本篇应用笔记着重介绍需要进行函数优化的相关测试。
3.1 测试项目介绍
由 图 1 可以知道,NETC 模块中的 ENETC1 作为一个独立的 Endpoint 通过一个伪 MAC 与 Switch 的端口 4 相连。对于 Switch 来说,可以将端口 4 所连接的 ENETC1 等同为外部连接到 Switch 的设备。NETC 中的 Switch模块支持的 5 Gbps 的转发速率即 5 个 1 G 端口各自达到 1 Gbps 的线速情况下的转发速率。从 Switch 端转发过来的数据在到达 ENETC1 后,会通过 SI 传递给 CPU 进行处理,所以 CPU 的运行速率也会成为性能测试中一个可能的瓶颈,为了尽量降低 CPU 的处理所带来的影响,测试程序中将发送与接收配置为使用同样一片缓冲区,并且在收到数据后,仅仅修改源 MAC 地址及目的 MAC 地址然后发送出去以让 CPU 的开销达到最低。
RT1180 是一颗双核的 MCU,包含一个主频为 240 MHz 的 Cortex-M33 核以及一个主频为 800 MHz 的 CortexM7 核,由于有 CPU 参与帧的修改,需要使用一个频率更高的核来处理 Switch 转发的数据以获取最佳性能,所以本次测试使用速度更快的 M7 核来进行。
图 2 为本测试的结构框图,此测试的目的是测出 Switch 的最高转发性能,RT1180 中的 NETC Switch 支持 5 个端口一共 5 Gbps 的速率,但是由于它有一个端口不是对外的物理端口,外接的测试仪器 Spirent Test Center 只能接入 4 个外部端口进行数据发送与接收,所以测试设计了如 图 2 这种测试流的走向。
Test Center 向 Switch 的:
• 端口 0 发送一条目的地址为端口 1 的流;
• 端口 1 发送一条目的地址为端口 2 的流;
• 端口 2 发送一条目的地址为端口 3 的流;
• 端口 3 发送一条目的地址为端口 4 的流。
端口 4 即为与内部 ENETC 相连的端口,CPU 接收到转发过来的流以后,修改目的 MAC地址为端口 0 并向端口4 发送,交换机会将这条流继续转发到端口 0。如此通过 Test Center的统计,就能测出 Switch 能否达到最高的 5Gbps 的转发速率。
3.2 测试软件配置
本测试的软件配置上主要分为两个部分,一个是 Switch 的初始化,一个是 ENETC Endpoint 的处理配置。其中Switch 的初始化可以参考《i.MX RT1180 NETC Switch 与 Endpoint 介绍》(文档 AN13842)里的介绍。初始化完成后,Endpoint 的部分需要对 SDK 中的 APP_SWT_Port1_XferLoopBack() 函数进行一些修改。
首先, SDK 的 demo 中的 loopback 测试的数据流走向是 ENETC 直接通过 Switch 的端口 1 发送出去,而不经过 Switch 内部的转发矩阵。所以需要将 APP_SWT_Port1_XferLoopBack() 函数中 Switch 管理端口部分的配置删除,添加 Endpoint 部分的 Tx BD ring 的配置,并且需要将 Switch 发送函数 SWT_SendFrame() 修改为为EP_SendFrame()。
除此之外,根据测试系统的框图,M7 核的部分需要先接收端口 3 输入并且转发到端口 4 的数据流,所以需要将接收函数放在前面,并且在接收到数据帧后修改源 MAC 与目的 MAC 地址,然后再通过 EP_SendFrame() 将数据帧发送到 Switch 的端口 4。
开始测试时,先使用原本 SDK 中所提供的接收以及发送函数来进行。修改后的测试函数如以下代码所示。其中,在外部的 4 个端口连接上 Test Center 后,M7 将会首先发送一些帧到 Switch 的端口 4 以让 Switch 来进行MAC 地址学习,学习完成之后,端口 4 即与发出学习帧的源 MAC 地址绑定。
- status_t APP_SWT_Port1_XferChain(void)
- {
- status_t result = kStatus_Success;
- netc_rx_bdr_config_t rxBdrConfig = {0};
- netc_tx_bdr_config_t txBdrConfig = {0};
- netc_bdr_config_t bdrConfig = {.rxBdrConfig =
- &rxBdrConfig, .txBdrConfig = &txBdrConfig};
- netc_buffer_struct_t txBuff = {.buffer = &g_txFrame, .length =
- sizeof(g_txFrame)};
- netc_frame_struct_t txFrame = {.buffArray = &txBuff, .length =
- 1};
- swt_mgmt_tx_arg_t txArg = {0};
- bool link = false;
- netc_msix_entry_t msixEntry[2];
- ep_config_t g_ep_config;
- uint32_t msgAddr;
- uint32_t length;
- /* MSIX and interrupt configuration. */
- MSGINTR_Init(MSGINTR1, &msgintrCallback);
- msgAddr = MSGINTR_GetIntrSelectAddr(MSGINTR1, 0);
- msixEntry[0].control = kNETC_MsixIntrMaskBit;
- msixEntry[0].msgAddr = msgAddr;
- msixEntry[0].msgData = EXAMPLE_TX_INTR_MSG_DATA;
- msixEntry[1].control = kNETC_MsixIntrMaskBit;
- msixEntry[1].msgAddr = msgAddr;
- msixEntry[1].msgData = EXAMPLE_RX_INTR_MSG_DATA;
- bdrConfig.rxBdrConfig[0].bdArray = &g_rxBuffDescrip[0][0];
- bdrConfig.rxBdrConfig[0].len = EXAMPLE_EP_RXBD_NUM;
- bdrConfig.rxBdrConfig[0].extendDescEn = false;
- bdrConfig.rxBdrConfig[0].buffAddrArray = &rxBuffAddrArray[0][0];
- bdrConfig.rxBdrConfig[0].buffSize = EXAMPLE_EP_RXBUFF_SIZE_ALIGN;
- bdrConfig.rxBdrConfig[0].msixEntryIdx = EXAMPLE_RX_MSIX_ENTRY_IDX;
- bdrConfig.rxBdrConfig[0].enThresIntr = true;
- bdrConfig.rxBdrConfig[0].enCoalIntr = true;
- bdrConfig.rxBdrConfig[0].intrThreshold = 1;
- bdrConfig.txBdrConfig[0].bdArray = &g_txBuffDescrip[0][0];
- bdrConfig.txBdrConfig[0].len = EXAMPLE_EP_TXBD_NUM;
- bdrConfig.txBdrConfig[0].dirtyArray = &g_txDirty[0][0];
- bdrConfig.txBdrConfig[0].msixEntryIdx = EXAMPLE_TX_MSIX_ENTRY_IDX;
- bdrConfig.txBdrConfig[0].enIntr = true;
- SWT_GetDefaultConfig(&g_swt_config);
- for (uint32_t i = 0U; i < 4u; i++)
- {
- g_swt_config.ports[i].ethMac.miiMode = kNETC_RgmiiMode;
- g_swt_config.ports[i].ethMac.miiSpeed = kNETC_MiiSpeed1000M;
- g_swt_config.ports[i].ethMac.miiDuplex = kNETC_MiiFullDuplex;
- g_swt_config.ports[i].bridgeCfg.isRxVlanAware = false;
复制代码- }
- /* Only set deafult forword in switch port1 and internel pseudo MAC
- port. */
- g_swt_config.bridgeCfg.dVFCfg.portMembership = 0x1FU;
- result = SWT_Init(&g_swt_handle, &g_swt_config);
- if (result != kStatus_Success)
- {
- return result;
- }
- PRINTF("Wait for PHY link up...\r\n");
- /* Wait link up */
- do
- {
- APP_PHY_GetLinkStatus(EXAMPLE_SWT_PORT0, &link);
- } while (!link);
- while(1);
- /* Unmask MSIX message interrupt. */
- EP_MsixSetEntryMask(&g_ep_handle, EXAMPLE_TX_MSIX_ENTRY_IDX,
- false);
- EP_MsixSetEntryMask(&g_ep_handle, EXAMPLE_RX_MSIX_ENTRY_IDX,
- false);
- APP_BuildBroadCastFrame();
- (void)EP_GetDefaultConfig(&g_ep_config);
- g_ep_config.si = kNETC_ENETC1PSI0;
- g_ep_config.siConfig.txRingUse = 1;
- g_ep_config.siConfig.rxRingUse = 1;
- g_ep_config.reclaimCallback = APP_ReclaimCallback;
- g_ep_config.msixEntry = &msixEntry[0];
- g_ep_config.entryNum = 2;
- result = EP_Init(&g_ep_handle, &g_macAddr[0],
- &g_ep_config, &bdrConfig);
- if (result != kStatus_Success)
- {
- return result;
- }
- for(int i=0; i<480; i++)
- {
- result = EP_SendFrame(&g_ep_handle, 0, &txFrame, NULL, NULL);
- if (result != kStatus_Success)
- {
- PRINTF("\r\nTransmit frame failed!\r\n");
- return result;
- }
- EP_WaitUnitilTxComplete(&g_ep_handle, 0);
- EP_ReclaimTxDescriptor(&g_ep_handle, 0);
- }
- txBuff.buffer = &g_rxFrame;
- txBuff.length = 60;//508;
- while (1)
- {
- do
- {
- result = EP_GetRxFrameSize(&g_ep_handle, 0, &length);
- if ((result != kStatus_NETC_RxFrameEmpty) && (result !=
- kStatus_Success))
复制代码- {
- return result;
- }
- } while (length == 0);
- result = EP_ReceiveFrameCopy(&g_ep_handle, 0, g_rxFrame,
- length, NULL);
- //Daisy chain test
- memcpy(&g_rxFrame[6], &g_macAddr[0], 6);
- memcpy(&g_rxFrame[0], &g_port1_macAddr[0], 6);
- result = EP_SendFrame(&g_ep_handle, 0, &txFrame, NULL, NULL);
- if (result != kStatus_Success)
- {
- PRINTF("\r\nTransmit frame failed!\r\n");
- return result;
- }
- EP_WaitUnitilTxComplete(&g_ep_handle, 0);
- EP_ReclaimTxDescriptor(&g_ep_handle, 0);
- }
- return result;
- }
复制代码 4 优化测试接口
上述的测试代码并不能够测出最佳的转发性能,原因是 SDK 所提供的接收以及发送函数主要满足的是实际应用的需求而不能满足性能测试要最大化释放性能的需求。
经过实际的测试,对于跑在 800 MHz 速率下的 M7 核,使用 SDK 的接收函数接收数据,然后修改帧头,再使用发送函数发送,到最终这一帧发出去,经过了大概 2447 个周期。在这个时间内,按照 64 字节帧 1 G 的线速计算,收进来的帧有 2447÷800000000×1488095 = 5.3 (1488095 是帧大小为 64 字节时 1 G 以太网 1 s 内发送的最大帧数量),所以收发一帧的时间内会额外再收进大概 4.3 帧的数据,而因为 Buffer Descriptor Ring (BDR)
长度的限制以及收发函数的构造,多收的帧就会被直接丢弃。所以需要对测试流程进行优化以能够匹配上 TestCenter 所发送的帧速度。
测试接口的优化是基于简化对 BDR 的操作,所以首先介绍一下 RT1180 NETC 模块中的 BDR 机制。BDR 中存放了多个 BD,每个 BD 可以用于接收硬件接到的数据帧或向硬件传递要发送的包,TX BDR的结构如图 3所示。
TX BDR 中有两个重要的索引值,分别是 Producer Index (PI) 和 Consumer Index (CI),它们控制了 BD 的运行过程。图 3 中配置了一个有效长度为 8 的 TX BDR,CI 及 PI 都由软件进行初始化,软件根据已经配置好的 BD数目,修改 PI 到相应位置,硬件开始根据 BD 的内容发送数据,每发送完成 1 帧,硬件会将 CI 加 1。软件可以读取 CI 的当前状态值,如果在多次传输的中间读取,这个值可能会跟实际的 CI 不一致。Tx BDR 的空与满由 CI与 PI 决定,当 PI 与 CI 相等时,BDR 为空,当 CI 比 PI 小 1 时,BDR满。RX BDR 与 TX BDR 相反,由硬件更新 PI,软件修改 CI。
此处需要注意的是,为了尽量缩短拷贝数据帧的时间,在进行优化时,TX BD与RX BD使用相同的数据buffer,这就需要 TX BD 与 RX BD 间协调好资源,避免发生数据 buffer 同时操作的冲突,这里也是由多个索引值的使用来实现的。
4.1 发送函数优化
现有的 SDK 的发送函数遵循的是一个较为一致的原则,就是只提供底层的发送单个帧的函数 EP_SendFrame()函数。并且这个函数中会有很多与拓展 BD 相关的判断内容,这些内容并不适用于性能测试,所以需要进行一些优化用于支持多个帧一起注册发送。优化的发送函数去除了对扩展 BD 的支持项,仅仅将相应的需要发送的帧都注册进 BDR,并且配制好 TX BDR的 PI,然后让硬件直接开始发送。优化后的发送函数如下:
- status_t EP_SendMultiFrame_Napi(ep_handle_t *handle,
- netc_tx_bdr_t *txBdRing,
- uint8_t hwRing,
- netc_frame_struct_t *frame,
- uint8_t number)
- {
- status_t result = kStatus_Success;
- netc_buffer_struct_t *txBuff = frame->buffArray;
- uint32_t address;
- netc_tx_bd_t *txDesTemp = NULL;
- txBuff = frame->buffArray;
- for (uint32_t bdIndex = 0; bdIndex < number; bdIndex++)
- {
- /* Get latest Tx BD address and clean it content. */
- txDesTemp = &txBdRing->bdBase[txBdRing->producerIndex];
- memset(txDesTemp, 0, sizeof(netc_tx_bd_t));
- #if defined(FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET) &&
- FSL_FEATURE_MEMORY_HAS_ADDRESS_OFFSET
- address =
- (uintptr_t)MEMORY_ConvertMemoryMapAddress((uint32_t)(uint32_t
- *)txBuff->buffer, kMEMORY_Local2DMA);
- #else
- address = (uintptr_t)(uint32_t *)txBuff->buffer;
- #endif
- /* Copy user Tx descriptors to hardware Tx BD. */
- txDesTemp->standard.flags = 0;
- txDesTemp->standard.addr = address;
- txDesTemp->standard.bufLen = txBuff->length;
- txDesTemp->standard.frameLen = txBuff->length;
- txDesTemp->standard.isExtended = 0;
- txDesTemp->standard.enableInterrupt = 0;
- txDesTemp->standard.isFinal = 1;
- /* Increase tx buffer address index and producer index. */
- txBuff++;
- txBdRing->producerIndex = EP_IncreaseIndex(txBdRing->producerIndex,
- txBdRing->len);
- }
- /* Make sure all data in the Tx BD is ready. */
- __DSB();
- /* Active Tx. */
- NETC_SISetTxProducer(handle->hw.si, hwRing, txBdRing->producerIndex);
- return result;
- }
复制代码 在设置完Tx PI后,硬件开始根据注册的BD发送数据帧,软件通过读取TxCI判断当前已发送的帧数,根据这个值来释放Rx BD以便能够接收下面的帧。
4.2 接收函数优化
关于接收数据帧流程的优化,原本的 SDK 代码里使用的是 EP_GetRxFrameSize() 函数来轮询是否接收到了数据帧,再使用 EP_ReceiveFrameCopy() 将数据帧拷贝到应用层的数据存储数组中。
这两个函数本身有一定的 CPU 开销,同时数据拷贝的过程也会有一定的开销,所以此处的接收流程需要进行优化以缩减 CPU 的开销。
根据 BDR 的特性可知,是否有帧接收到可以直接通过读取 RX BDR 的 PI 并与先前的 RX PI 进行比较来判断。所以接收流程即可简化为设置两个变量,分别存储当前读取的 RX PI的值以及之前的 RX PI 的值,使用当前的 RX PI 值。
通过这种优化一次接收加一次发送的 CPU 开销可以分摊到多个帧的接收与发送上,相比于之前收一帧,发一帧,再接着收下一帧的模式,这样的收发方式在合适的 BDR 的长度上就可以达到最高的性能。本实验中所使用的 BDR 长度为 24。
详细内容见附件:
i.MX RT1180 NETC 性能测试的接口优化.pdf
(552.62 KB, 下载次数: 4)
|
|