关于mcpy指令使用介绍
1、指令介绍
内存拷贝指令mcpy,可实现从源地址到目标地址的连续内存字节拷贝。
该指令汇编格式如下:
mcpy rs1, rs2, rs3
其中,其中,rs1指定拷贝结束地址(不包含该地址),rs2指定拷贝起始地址,rs3指定目标起始地址。指令执行期间,硬件自动从源起始地址逐字节读取数据,并写入至目标起始地址,直至源地址累加至结束地址为止。
该指令可用于替代memcpy函数, 实现连续的内存搬运功能,指令寄存器中保存起始地址,目标地址,结束地址的地址信息,所有地址均无对齐要求。
2、指令使用
CH32V407支持mcpy指令,在官方软件开发库(SDK)环境下,用户可直接调用封装接口 ASM_MCPY() 来使用该指令,该函数位于core_riscv.h文件中,代码如下:
/********************************************************************* * @fn ASM_MCPY * * @brief Implement the assembly instruction function of mcpy, copy * the continuous data from the starting address of SrcAddrStart * to the ending address of SrcAddrEnd to the starting address * of DstAddrStart.(Only for V3F) * * @param SA - Copy the start address of the source region. * EA - Copy the end address of the source region. * DA - Copy the start address of the destination region. * * @return none */ static inline void ASM_MCPY(uint8_t* DA,uint8_t* SA,uint8_t* EA) { __asm__ volatile("mcpy %2, %0, %1" :"+r"(SA) , "+r"(DA):"r"(EA):"memory"); }
关于DA、SA、EA三个入口参数,解释如下:
- DA - 复制目标区域的起始地址。
- SA - 源区域的起始地址。
- EA - 复制源区域的结束地址。
3、指令测试
(1)与memcpy性能对比测试
测试时基:
以TIM2作为测试时基,1us计数一次
测试方法:
以256 B、1024 B、4096 B 三种拷贝长度各固定搬运总量 512 KB(迭代次数分别为 2048 / 512 / 128 次),两者使用完全相同的 4 字节对齐源/目的缓冲区与相同的循环结构;每一轮结束后都对目的缓冲区做逐字节比对,确保两种方式的结果数据一致
测试输出:
测试输出四类可量化指标:
- 总耗时(us,直接体现拷贝时间)
- 吞吐速率(MB/s,由于 1 byte/us 即 1 MB/s,该值可直接反映搬运效率)
- 每字节周期数(cycle/byte,反映单字节搬运占用的CPU成本)
- 两者耗时比(mcpy 时间占memcpy时间的百分比,一眼即可判定哪一方更快)
测试代码:
/********************************************************************* * @fn Copy_Report * * @brief Benchmark the mcpy instruction and the C library memcpy, * and print the copy time and the copy efficiency. 1 byte/us * is equal to 1 MB/s. * * @param len - length of the data copied in one time. * * @return none */ void Copy_Report(uint16_t len) { uint32_t bytes = 0; uint32_t mcpy_us = 0; uint32_t memcpy_us = 0; uint32_t cycles = 0; u16 iter = 0; iter = BENCH_TOTAL / len; bytes = (uint32_t)len * iter; printf("BENCH: LEN=%d, ITER=%d, BYTES=%d\r\n", len, iter, bytes); mcpy_us = Copy_Run(len, iter, 0); printf(" MCPY : DATA %s, TIME %d us, SPEED %d MB/s", Copy_Verify(len) ? "FAIL" : "PASS", mcpy_us, bytes / mcpy_us); cycles = mcpy_us * (SystemCoreClock / 1000000) * 100 / bytes; printf(", %d.%02d cycle/byte\r\n", cycles / 100, cycles % 100); memcpy_us = Copy_Run(len, iter, 1); printf(" MEMCPY: DATA %s, TIME %d us, SPEED %d MB/s", Copy_Verify(len) ? "FAIL" : "PASS", memcpy_us, bytes / memcpy_us); cycles = memcpy_us * (SystemCoreClock / 1000000) * 100 / bytes; printf(", %d.%02d cycle/byte\r\n", cycles / 100, cycles % 100); printf(" RATIO : MCPY time = %d%% of MEMCPY time\r\n", (mcpy_us * 100) / memcpy_us); }
u16 n = 0; uint16_t t0 = 0; uint16_t t1 = 0; u16 bench_len[BENCH_ITEM] = {256, 1024, 4096}; TimeBase_Init(); printf("TIMEBASE: TIM2 1MHz, 1 tick = 1us\r\n"); t0 = TimeBase_GetTick(); Delay_Ms(50); t1 = TimeBase_GetTick(); printf("TIMEBASE CHECK: Delay_Ms(50) = %d us\r\n", TimeBase_Elapsed(t0, t1)); for(n = 0; n < BENCH_BUF_LEN; n++) { src_buf[n] = n & 0xFF; dst_buf[n] = 0xA5; } printf("MCPY & MEMCPY BENCHMARK START\r\n"); for(n = 0; n < BENCH_ITEM; n++) { Copy_Report(bench_len[n]); } printf("MCPY & MEMCPY BENCHMARK END\r\n"); printf("MCPY TEST END\r\n");
测试结果:

由截图打印结果可知,使用mcpy指令相较于memcpy函数会快上很多。
(2)中断响应行为测试
mcpy指令为可中断指令。在mcpy执行过程中,若检测到有效中断请求,硬件将在当前字节传输完成后(或下一个指令周期边界处)暂缓执行。中断返回后,指令继续执行,拷贝流程与未发生中断时的最终结果一致。该机制保证了大块数据传输场景下系统的实时响应能力。
测试中断:
以TIM3更新中断为中断源,设置1us计数一次,每5us进一次中断;
测试方法:
利用PA0、PA1两个引脚:
对于PA0,执行ASM_MCPY函数之前拉高,执行完成后拉低;
对于PA1,每进一次TIM3中断改变一次引脚电平,电平变化次数代表进入中断次数。
在拷贝完成之后,对源缓冲区、目的缓冲区数据进行对比,若数据一致,则表示在该过程中,即便又中断产生,也不会对拷贝结果产生影响
测试输出:
PA0、1引脚波形、源缓冲区、目的缓冲区数据对比结果
测试代码:
for(n = 0; n < TRACE_LEN; n++) { src_buf[n] = (u8)n; dst_buf[n] = 0; } TIM_Cmd(TIM3, ENABLE); GPIOA->BSHR = GPIO_Pin_0; ASM_MCPY(dst_buf, src_buf, src_buf + TRACE_LEN); GPIOA->BCR = GPIO_Pin_0; TIM_Cmd(TIM3, DISABLE); if(MCPY_Compare(src_buf, dst_buf, TRACE_LEN) == 0) { printf("MCPY SUCCESS\r\n"); } else { printf("MCPY FAIL\r\n"); }
/********************************************************************* * @fn TIM3_IRQHandler * * @brief TIM3 update interrupt service routine. It counts the * interrupt and toggles PA1, so that the interrupt activity * can be observed on an oscilloscope while the mcpy * instruction is running: PA0 is high during the copy, and * PA1 keeps toggling only if the interrupt is served inside * the copy. * * @return none */ void TIM3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); if(isr_pin == 0) { isr_pin = 1; GPIOA->BSHR = GPIO_Pin_1; } else { isr_pin = 0; GPIOA->BCR = GPIO_Pin_1; } } }
测试结果:


由截图波形以及打印结果可知,在内存拷贝期间,允许中断响应,且不会对拷贝结果产生影响。
4、注意事项
(1)地址对齐与重叠限制
mcpy指令执行从低地址向高地址方向的顺序数据搬运。严禁在源数据区的尾端与目标数据区 的首端存在地址重叠时使用本指令。若发生此类重叠,将导致源数据在被读取前被目标写操作覆盖,进而引发搬运结果错误。
(2)异常处理
若mcpy指令的源地址或目标地址触发以下保护机制,将产生不可恢复(不可恢复)异常,系统需进行上层复位或错误处理:
- 物理内存保护(PMP)访问权限限制
- 硬件断点(Hardware Breakpoint)命中
(3)寄存器副作用
mcpy指令执行期间,源地址寄存器(rs2)和目标地址寄存器(rs3)会随搬运进度递增更新。
指令执行完成后,rs2 与 rs3 的最终值取决于搬运终止时的地址位置,不可预测,用户不应依赖其后续值进行编程。

浙公网安备 33010602011771号