How does __delay_cycles work?

I'm using a msp430g2553 and setting the clock to 1Mhz:

BCSCTL1 = CALBC1_1MHZ;
DCOCTL = CALDCO_1MHZ;


I've timed a 5 million cycles and it unexpectedly takes around 6 seconds (I was expecting 5). The loop I've used is:

int i;
for(i=0;i<5000;i++)
__delay_cycles(1000);


I've read about using interrupts (probably based on Timer_A, etc) but I'd like to avoid that for now.

So my question is: how can I implement a constant argument sleep based on __delay_cycles?

Later edit

Assembly for:

void delay1000() {
__delay_cycles(1000);
}


is:

delay1000:
0c3b4: 04 12                     PUSH    R4
0c3b6: 04 41                     MOV     SP,      R4
0c3b8: 24 53                     INCD    R4
0c3ba: 3f 40 4c 01               MOV     #0x014c, R15    // 332 decimal (1000/3 -> DEC, TST and JNZ)
0c3be: 1f 83                     DEC     R15
0c3c0: 0f 93                     TST     R15
0c3c2: fd 23                     JNZ     delay1000+0xa
0c3c4: 03 43                     NOP
0c3c6: 03 43                     NOP
0c3c8: 34 41                     POP     R4
0c3ca: 30 41                     RET

-

Look at the dissassembly of the code, remember the for loop adds some instructions which take a few cycles. With a 5000 loop, these extra cycles will add up. Also, how delay_cycles() is implemented can make a big difference.
You can count the cycles for each loop in the simulator, do the math and compensate for the added cycles by passing the appropriate value.

Of course, the best way to get accurate timing is to use interrupts, but you say you want to avoid those for now.

-
I don't know about this particular microcontroller, but a loop like this potentially breaks the CPU's pipelining, resulting in a relatively large timing error. –  jippie Jan 3 '13 at 14:03
That depends on the contents of __delay_1000, which we don't know. As far as the outer loop is concerned, there isn't much pipelining to worry about in an MSP430. –  Brian Drummond Jan 3 '13 at 14:20
I didn't expect a simple loop comparison to account for such a difference but after looking at how __delay_cycles is implemented it makes sense. Thanks for the answer. –  diciu Jan 3 '13 at 14:33
@diciu - Yes, you're right, such a large difference won't all be due to the added instructions - these delay routines are difficult to write to accommodate a very large range timescale-wise, so they often lose accuracy at the extreme ends of the scale. As long as you can look at the implementation/disassembly though, you can adjust things to make sure your code works as intended. –  Oli Glaser Jan 3 '13 at 14:58

From the MSP430 Optimizing C/C++ Compiler v 3.1 User's Guide (SLAU132c.pdf) pp. 109:

The __delay_cycles intrinsic inserts code to consume precisely the number of specified cycles with no side effects. The number of cycles delayed must be a compile-time constant.

It is a intrinsic function, specifically designed to use up cycles. It should be good for at least that number of cycles. You can change 1000 to something else (it must be a constant, can't be a variable). Both the for loop and the function call will add to the delay.

yes with the code there will be a small "offset" and "gain" error in the actual delay you get. The code is good to guarantee a certain minimum delay, with a very small error into the positive direction. That's why the delay inside the loop was chosen rather large ("ms"). If you need a dead-on delay, the best way would be using a timer. But if you for some reason still want to use SW delays, I would recommend coding a function in assembler rather than measuring the C function. You could compensate in the function itself for the "offset" and "gain" errors of the call and loop overhead. Also, our IDEs have cycle counter functions that you could use to analyze the delay function. But for SW delays, be aware that interrupts could kick in (if enabled) and change your resulting observable delay.

TI Employees on their support forums.

-
Thanks for the answer. On the msp430 is one instruction always executed within 1 cycle? –  diciu Jan 3 '13 at 15:40
Depends on the instruction, optimization, compile options or language, but an intrinsic like __delay_cycles is highly optimized for that purpose, as well as "nops" (1 cycle). Higher order math take more, math involving things outside of 2/4/8/16 take more. Found a bit on port bit changing times:You can't do this faster in ASM. Bit wise operations on ports normally take 4 cycles, C or ASM. If you use the low nibble of the port then the CPU uses the Constant Generator (1,2,4,8) which saves a cycle (and code). Thus the fastest you can go is 3 cycles / bit operation. –  Passerby Jan 3 '13 at 16:01