关于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");

测试结果:

image

由截图打印结果可知,使用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;
        }
    }
}

测试结果:

image

image

由截图波形以及打印结果可知,在内存拷贝期间,允许中断响应,且不会对拷贝结果产生影响。

 

4、注意事项

(1)地址对齐与重叠限制

mcpy指令执行从低地址向高地址方向的顺序数据搬运。严禁在源数据区的尾端与目标数据区 的首端存在地址重叠时使用本指令。若发生此类重叠,将导致源数据在被读取前被目标写操作覆盖,进而引发搬运结果错误。

(2)异常处理

若mcpy指令的源地址或目标地址触发以下保护机制,将产生不可恢复(不可恢复)异常,系统需进行上层复位或错误处理:

  • 物理内存保护(PMP)访问权限限制
  • 硬件断点(Hardware Breakpoint)命中

(3)寄存器副作用

mcpy指令执行期间,源地址寄存器(rs2)和目标地址寄存器(rs3)会随搬运进度递增更新。

指令执行完成后,rs2 与 rs3 的最终值取决于搬运终止时的地址位置,不可预测,用户不应依赖其后续值进行编程。

 

posted @ 2026-10-29 13:43  ZaiLi  阅读(5)  评论(0)    收藏  举报