我正在尝试使用ARM编译器5 armcc编译的.c文件中的内联汇编中的STM / LDM指令生成 AXI 总线突发访问。
inline void STMIA2(uint32_t addr, uint32_t w0, uint32_t w1)
{
__asm {
STMIA addr!, { w0, w1 }
}
}
但ARM编译器armcc用户指南第7.18段说: “所有LDM和STM指令都扩展为具有相同效果的LDR和STR指令序列。但是,编译器可能会在优化期间将单独的指令重新组合成LDM或STM。”
这就是实际发生的事情,LDM / STM在某些情况下会扩展为一组LDR / STR,这些指令的顺序是任意的。 这会影响性能,因为我们使用的HW针对突发处理进行了优化。这也打破了功能正确性,因为我们使用的HW考虑了单词序列并忽略了偏移(但编译器认为更改指令的顺序是安全的。)
要解决此问题,可以使用嵌入式汇编程序而不是内联汇编程序,但这会导致额外的函数调用 - 返回影响性能的因素。
所以我想知道是否有办法在不损失性能的情况下正确生成LDM / STM?我们能够在GCC中做到这一点,但没有为armcc找到任何解决方案。
目标CPU:Cortex M0 +(ARMv6-M)。
修改 从设备都是片上设备,大多数都是非存储设备。对于支持突发访问区域的非内存从站的每个寄存器都保留了地址空间(例如[0x10000..0x10100]),我不完全确定为什么,可能CPU或总线不支持固定(非增量) )地址。 HW忽略该区域内的偏移。例如,完整请求可以是16字节,完整请求的第一个字是第一个字写入(即使偏移非零)。
答案 0 :(得分:1)
所以我想知道是否有办法在不损失性能的情况下正确生成LDM / STM?我们能够在GCC中做到这一点,但没有为armcc找到任何解决方案。
关于编译器优化的一点点。 Register allocation是其中最艰巨的工作之一。任何编译器代码生成的核心可能是在分配物理CPU寄存器时。大多数编译器使用Single static assignment or SSA将“C”变量重命名为一堆伪变量(或时间顺序变量)。
为了让您的STMIA和LDMIA工作,您需要加载和存储保持一致。即,如果它是stmia [rx], {r3,r7}
并且恢复如ldmia [rx], {r4,r8}
,其中'r3'映射到新的'r4'并且存储的'r7'映射到恢复的'r8'。对于任何编译器来说,这一点并不简单,因为“C”变量将根据需要进行分配。同一变量的不同版本可能在不同的寄存器中。要使stm/ldm
工作,必须分配这些变量,以便寄存器以正确的顺序递增。即,对于上面的ldmia
,如果编译器想要r7
中存储的r0
(可能是返回值?),则无法创建好ldm
没有生成额外代码的指令。
你可能已经获得了gcc来生成这个,但它可能是运气。如果你只进行gcc,你可能会发现它不起作用。
有关GCC stm / ldm的问题,请参阅:ldm/stm and gcc。
举个例子,
inline void STMIA2(uint32_t addr, uint32_t w0, uint32_t w1)
{
__asm {
STMIA addr!, { w0, w1 }
}
}
inline
的值是整个函数体可以放在代码中。调用者可能在寄存器R8和R4中具有w0
和w1
。如果函数不是inline
,那么编译必须将它们放在R1和R2中,但可能产生了额外的移动。任何编译器都难以一般地满足ldm/stm
的要求。
这会影响性能,因为我们使用的HW针对突发处理进行了优化。这也打破了功能正确性,因为我们使用的HW考虑了单词序列并忽略了偏移(但编译器认为更改指令的顺序是安全的。)
如果硬件是总线上特定的非内存从属外设,那么您可以在外部包装器中包装写入此从属的功能并强制进行寄存器分配(请参阅AAPCS)以便{{ 1}}会起作用。这将导致性能损失,可以通过设备驱动程序中的某个自定义汇编程序来缓解。
然而,听起来设备可能是内存?在这种情况下,您遇到了问题。通常,像这样的内存设备只会使用缓存吗?如果您的CPU具有MPU(内存保护单元)并且可以同时启用数据和代码缓存,则可以解决此问题。高速缓存行将始终是突发访问。只需要在代码中注意设置MPU和数据缓存。 OP Cortex-M0 +没有缓存,设备是非内存所以这是不可能的(也不需要)。
如果您的设备是内存并且您没有数据缓存,那么您的问题可能无法解决(无需大量工作)并且您需要不同的硬件。或者你可以像外围设备一样包装它并获得性能提升;失去了随机访问存储设备的好处。