2015-04-07 57 views
3

更改Raspberry Pi上GPIO引脚的上拉/下拉电阻要求在根据规格启用和解除时钟信号后等待150个周期。多做一点不会伤害,但使用定时器等待的时间会更长,所以我不想这样做。所以我有这个简单的繁忙循环:如何在ARM上添加150个周期的延迟?

for (int i = 0; i < 150; ++i) { asm volatile (""); } 

0: e3a03096  mov  r3, #150  ; 0x96 
4: e2533001  subs r3, r3, #1 
8: 1afffffd  bne  4 <foo+0x4> 

循环150次,执行300条指令。如果没有指令缓存,并且没有分支预测,那肯定会超过150个周期。但是一旦开启了这个循环,我认为这个循环的运行速度会快于150个循环。

那么如何使用或不使用指令缓存并启用分支预测来等待接近150个周期? 注意:最坏的情况可能是2个函数,delay_no_cache()和delay_cache()

这不是How to delay an ARM Cortex M0+ for n cycles, without a timer?的重复,因为指令缓存和分支预测完全抛出了时间。 Raspberry Pi(ARMv6)和Raspberry Pi2(ARMv7)的时序也不同。如果有人会插入一个DMB,DSB(我猜这些会是NOP,因为不是RAM)或ISB指令进入循环,有谁知道执行时间(有没有缓存)?这将防止启用缓存时的失控效果吗?

+0

可能重复的[如何延迟了ARM Cortex M0 + n个循环,如果没有计时器?](http://stackoverflow.com/questions/27510198/how-to-delay-an-arm-cortex -m0-for-n-cycles-without-a-timer) – samgak

+0

nop 150次... – leppie

+0

不重复。意想不到的是,我正在寻找一个循环,它具有(几乎)相同的指令时序,无需指令高速缓存和分支预测。我知道上面的简单循环运行速度比指令缓存和分支预测快500倍。 –

回答

0

您可能需要使用REPEAT宏功能来做你的延迟。在运行时使用循环,总会有优化,循环本身也需要花费时间。你可以迭代一个NOP的宏达150次。没有优化,也没有冗余循环。

这里有一个重复宏的模板:

#define MACRO_CMB(A , B)   A##B 
#define M_RPT(__N, __macro)   MACRO_CMB(M_RPT, __N)(__macro) 

#define M_RPT0(__macro) 
#define M_RPT1(__macro)    M_RPT0(__macro) __macro(0) 
#define M_RPT2(__macro)    M_RPT1(__macro) __macro(1) 
#define M_RPT3(__macro)    M_RPT2(__macro) __macro(2) 
... 
#define M_RPT256(__macro)   M_RPT255(__macro) __macro(255) 

您可以定义NOP指令是这样的:

#define MY_NOP(__N)     __asm ("nop"); // or sth like "MOV R0,R0" 

然后你就可以只调用该重复指令150次

M_RPT(150, MY_NOP); 

它真的会被执行150次。

希望这有助于。

+1

这完全没有帮助。这个问题并不是最优化的,因为asm()阻止了这一点。这也不是循环本身的成本。该费用可以在延期时考虑,也可以通过展开将其降至最低。问题是,150 NOP没有缓存需要150个周期,但只有15个周期(或类似的情况)。 –

1

我在我的Raspberry PI 2上运行延迟()进行了一些测量,计算10个因子中的1-100000000个循环,并计算了时间过去后的循环计数。这表明没有高速缓存只需要通过一个空循环就足以满足150个周期的延迟(这只是sub + bcs)。一个单一的NOP(以150的顺序)需要32个(总共5030个)没有高速缓存的循环和1.5个循环(总共226.5个)。 orr; add; and; mov; orr; add; and; mov;循环还显示了流水线​​和CPU的超标量,每个操作码只需要0.15个循环。得到一个良好的时间循环不太好。

总之,我必须放弃,只是使用基于定时器的延迟。没有缓存,实际上这比快速缓存需要150个循环的循环更快。

void delay(uint32_t count) { 
    uint32_t a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; 
    while(count--) { 
// branch icache dcache cycles/loop 
// no  no  no  ~507 
// no  no  yes  43.005 
// no  yes no  1.005 
// no  yes yes  1.005 
// yes no  no  ~507 
// yes no  yes  43.005 
// yes yes no  1.005 
// yes yes yes  1.005 
// asm (""); 

// branch icache dcache cycles/loop 
// no  no  no  ~750 
// no  no  yes  67.500 
// no  yes no  16.500 
// no  yes yes  16.500 
// yes no  no  ~750 
// yes no  yes  67.500 
// yes yes no  16.500 
// yes yes yes  16.500 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 
// asm ("nop"); 

// branch icache dcache cycles/loop 
// no  no  no  ~505 
// no  no  yes  43.500 
// no  yes no  1.500 
// no  yes yes  1.500 
// yes no  no  ~505 
// yes no  yes  43.500 
// yes yes no  1.500 
// yes yes yes  1.500 
asm ("orr %0, %0, %0" : "=r" (a) : "r" (a)); 
asm ("add %0, %0, %0" : "=r" (b) : "r" (b)); 
asm ("and %0, %0, %0" : "=r" (c) : "r" (c)); 
asm ("mov %0, %0" : "=r" (d) : "r" (d)); 
asm ("orr %0, %0, %0" : "=r" (e) : "r" (e)); 
asm ("add %0, %0, %0" : "=r" (f) : "r" (f)); 
asm ("and %0, %0, %0" : "=r" (g) : "r" (g)); 
asm ("mov %0, %0" : "=r" (h) : "r" (h)); 

// branch icache dcache cycles/loop 
// no  no  no  ~1010 
// no  no  yes  85.005 
// no  yes no  18.000 
// no  yes yes  18.000 
// yes no  no  ~1010 
// yes no  yes  85.005 
// yes yes no  18.000 
// yes yes yes  18.000 
// isb(); 

// branch icache dcache cycles/loop 
// no  no  no  ~5075 
// no  no  yes  481.501 
// no  yes no  141.000 
// no  yes yes  141.000 
// yes no  no  ~5075 
// yes no  yes  481.501 
// yes yes no  141.000 
// yes yes yes  141.000 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
// isb(); 
    } 
} 
+0

'asm(“orr%0,%0,%0”:“= r”(a):“r”(a))''不使用'%1',所以它可能使用任何垃圾在输出操作数作为输入,如果编译器碰巧选择不同的输入/输出寄存器。尽管如此,您已经拥有足够的指令级并行性,即循环运行的依赖链不会对延迟产生瓶颈,所以它并不重要。 (我对每个指令0.15个周期持怀疑态度,即使AMD Ryzen每个时钟只能管理5条指令(如果有多重指令的话也可以处理6条指令),而Intel x86 CPU是4宽度超标量的,6.66宽度并不合理。 –

+0

哦,我想我知道发生了什么,你没有使用'asm volatile',并且你的函数在循环之后不使用'a,b,c,d,e,f,g,h',所以gcc (非易失性''asm'至少有一个输出操作数被认为是它的输入的纯函数,没有副作用。https://gcc.gnu.org/onlinedocs/gcc /Extended-Asm.html#Volatile).Yup,https://godbolt.org/g/Qg7r8N确认了如果你用'-O1'或更高编译出现的情况,你的函数甚至可以在x86上编译好,因为asm优化:P总是检查编译器的asm输出。 –