找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 230|回复: 0
收起左侧

STM32H7/cotex M内核 汇编,内嵌汇编小技巧

[复制链接]
ID:1147129 发表于 2025-11-6 02:39 | 显示全部楼层 |阅读模式
以下作为我个使用cotex  M7内核统计的一些特性:

一  内核行为分析
实例代码1:
FPU_Test: //double FPU_Test (double a_mul,double b_mul,double c_add);
        
        
        PUSH{R4-R7}
        MOV R0, #1024        //减少循环次数,避免缓存影响
    // 初始化完全独立的寄存器组

    LDR R1,=8234
        MOV R6,R0
        MOV R7,R1
        PLI [LR]
        
FPU_Test_Loop_Opt:
    // 8组完全独立的 VFMA 操作
    VFMA.F32 S2, S1, S0      // 组1
        SMLAL  R3,R2,R1,R0
    VFMA.F32 S3, S1, S0      // 组2 - 完全独立
        SUBS R4,R4, #1
        SHADD16 R5,R5,R0
        SMLAL  R3,R2,R1,R0
    VFMA.F32 S4, S1, S0      // 组3 - 完全独立
        SHADD16 R4,R4,R1
        SUBS R5,R5, #1
        SMLAL  R3,R2,R1,R0
    VFMA.F32 S5, S1, S0    // 组4 - 完全独立
        SHADD16 R5,R5,R0
        SUBS R4,R4, #1
        SMLAL  R3,R2,R1,R0
    VFMA.F32 S6, S1, S0   // 组5 - 完全独立
    SUBS R0,R0, #1
    BNE FPU_Test_Loop_Opt

        
        

        POP{R4-R7}
    BX LR


以上为keilv5  MDK V5.23 编译器语法
1:使用VFMA(浮点区域指令)与使用SMLAL(整数区域指令)能够让处理器进行双发射(在cotex  M7权威手册里亦有记载)
但是,注意需注意顺序即:
    VFMA.F32 S2, S1, S0      // 组1
    SMLAL  R3,R2,R1,R0


      VFMA.F32 S4, S1, S0      // 组3 - 完全独立
      SHADD16 R4,R4,R1
是能够正常双发射
    VFMA.F32 S2, S1, S0      // 组1
    SMLAL  R3,R2,R1,R0


      SHADD16 R4,R4,R1
      VFMA.F32 S4, S1, S0      // 组3 - 完全独立
以上只能单发射,而不能双发射

      SHADD16 R4,R4,R1
      SUBS R5,R5, #1
      SMLAL  R3,R2,R1,R0
      VFMA.F32 S5, S1, S0    // 组4 - 完全独立
   以上可以双发射
总结为:在使用浮点+整数指令时,下一条指令如果需要使用整数并且需要使用寄存器,那么MCU则不支持双发射,如果为浮点,即可成功双发射.
若持续为整数指令,那么在数据无依赖的情况下,即可双发射,否者只能单发射,或者阻塞 (均不包含除法指令)
1     针对于除法指令,在mcu上能不用就不用,无符号/有符号整数除法平均会消耗10个周期左右,除非你的结果较小,例如小于256.那么可以在较短的时间里得出结果,对于双精度浮点除法,通常需要14-16个时钟周期,单精度需要8-12个周期.除法指令是阻塞运行的,不支持单周期的吞吐量.除法比较特殊,即使数据无依赖也不行
2    在cotex-m7内核上,大部分都会有支持双精度浮点,但是,双精度浮点一般比单精度慢2-8倍,不同指令有着不同的效率,如VADD.F64就最快,2周期的吞吐量,基本与VADD.F32 的单周期差不了太多,对于像VFMA.F32(实例代码中的指令)为单周期吞吐量.  VFMA.F64不支持单周期吞吐量,执行一条需要7个周期,并且不支持与其他指令双发射,包括大部分的.F64的运算指令(像VMOV.F64这种执行时间为2周期,与双精度与单精度无关,执行周期按位宽/32bit)都不支持与其他指令双发射


二    性能优化:
对于需要性能优化的场景来说,手动添加(C#) __ASM    volatile{  "PLI  [这里填C里的一个变量]");用于提前预加载(提示内核等会儿要使用)需要执行的指令,可以在跳转后更快的执行,通常用于执行动态代码,例如:
__ASM volatile (
                        "push{r0,r1}\n"
                        "MOV R0,0X01 \n"
                        "ISB SY\n"
                        "PLI [R0]\n"
                        "ISB SY\n"
                        "pop {r0,r1}\n"
                );



__ASM  volatile (
                        "push {r0-r3,r12,lr} \n"
                        "MOV R0,0X01 \n"
                       "BLX R0 \n"
                        "pop {r0-r3,r12,lr} \n"
    );
中,将动态代码存放于ITCM内存中,代码就是实例代码1,存放地址0x00
对于需要跳转动态代码时,保存寄存器是一个必不可少的的操作,通常使用堆栈保存,通常建议堆栈始终为8字节对齐(在cotex  M系列内核权威手册里亦有记载) 所以在使用push的时候,建议一次性压入两个寄存器(64位)保持一直为8字节对齐.对于未对其的情况下,我也测试过了,首先效率会下降,对齐的情况,入栈8个字节仅需一个周期,无阻塞的发射,即可执行接下来的指令,未对其情况下,与使用STM  SP!{ }是等效的,需要1个周期解析指令+2个周期存入数据.

      其次未8字节对齐容易导致未定义行为,进入HardFault_Handler,这个问题隐藏的很隐蔽,使用Jlink单步调试是无法复现问题.(我没记错的话,权威手册里应该有记载,不过容易忽略,这里就提个醒)
      对于地址跳转,一定要保证,需要跳转的地址的最低位一定要为1, 例如你需要跳转到0x0800346,那么你实际要写入PC指针的地址一定是0x0800347,而不是0x0800346,否则也会导致未定义行为,进入HardFault_Handler,同样使用jlink无法复现,单步无法发现问题.


先记录到这里,如有新的勘误点会在更新,附件为cotex  M4系列内核汇编编程手册,全册中文,对于初学者来说很有用处

STM32F3与F4系列Cortex M4内核编程手册_中文版.pdf

6.71 MB, 下载次数: 0, 下载积分: 黑币 -5

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表