GP2X Cycle Latency


rixed

Member
Joined
Dec 31, 2005
Messages
206
Age
48
Location
Paris (fr)
Website
happyleptic.org
Hi y'all !

The ARM specs are a little too dry for me when cycle latency is concerned.

Im interlacing many reads/writes (most uncached) and I wonder how exactly to predict wait clock cycle for RAM access, register usage, and such things.

Any known doc out there for the ARMs 920/940 ?
 
Hi y'all !

The ARM specs are a little too dry for me when cycle latency is concerned.

Im interlacing many reads/writes (most uncached) and I wonder how exactly to predict wait clock cycle for RAM access, register usage, and such things.

Any known doc out there for the ARMs 920/940 ?
I'm pretty sure that uncached memory reads stall the fetch pipeline stage for a LONG time, though that time is not really a function of the ARM itself. This delay makes other issues pretty unimportant. But ignoring that, I think a basic rule of thumb is that you need to wait 2 cycles before using a loaded value, and brances are bad (3 cycles I think plus the pipeline flush). I invested in a great book about the ARM that goes into a lot of detail about this (I don't have it handy and forget the title, I can look at it later if you'd like). It's not cheap though.
 
Last edited by a moderator:
Hi y'all !

The ARM specs are a little too dry for me when cycle latency is concerned.

Im interlacing many reads/writes (most uncached) and I wonder how exactly to predict wait clock cycle for RAM access, register usage, and such things.

Any known doc out there for the ARMs 920/940 ?
I'm pretty sure that uncached memory reads stall the fetch pipeline stage for a LONG time, though that time is not really a function of the ARM itself. This delay makes other issues pretty unimportant. But ignoring that, I think a basic rule of thumb is that you need to wait 2 cycles before using a loaded value, and brances are bad (3 cycles I think plus the pipeline flush). I invested in a great book about the ARM that goes into a lot of detail about this (I don't have it handy and forget the title, I can look at it later if you'd like). It's not cheap though.

The best book I know for this is ARM System Developer's Guide : Designing and Optimizing System Software; it really contains everything you need to develop hardcore ARM stuff for this platform and in general. Anyway, cycle-counting wise it's almost exactly as Dzz says; uncached loads will stall until the value is loaded (exact latency depends on memory speed, obviously), a cached load will have a one cycle latency unless it's a byte, half-word (16-bits on an ARM), or signed load in which case it'll take another cycle to sign-extend or clear it. Using the value from a load in the next instruction will add extra cycles into the pipeline to interlock. Branches are indeed 3 cycles plus a pipeline flush.

The other ones to be wary of:

a register-based shift (e.g. mov r0,r0,lsr r1) costs an extra cycle.

a multiply instruction's timing is based on the value in the right hand register, so if you know that r1 is always > r0 then do mul r2,r1,r0 and not mul r2,r0,r1 (actual penalty depends on the where the first bit that differs from the sign bit is). I can dump a table here if it's not in the ARM9 manual.

And the corollary, optimisations:

a 32x32=64-bit multiply costs only one more cycle than a 32x32=32-bit multiply
an MLA / xMLAL costs the same as a MUL / xMULL

For those of us that are used to StrongARMs (and XScale), the ARM9 implementation is quite clean regarding penalties. :) The only problem is that existing heavily StrongARM/XScale optimised stuff is not optimised for the ARM9. Ho hum.
 
Last edited by a moderator:
Hi y'all !

The ARM specs are a little too dry for me when cycle latency is concerned.

Im interlacing many reads/writes (most uncached) and I wonder how exactly to predict wait clock cycle for RAM access, register usage, and such things.

Any known doc out there for the ARMs 920/940 ?
I'm pretty sure that uncached memory reads stall the fetch pipeline stage for a LONG time, though that time is not really a function of the ARM itself. This delay makes other issues pretty unimportant. But ignoring that, I think a basic rule of thumb is that you need to wait 2 cycles before using a loaded value, and brances are bad (3 cycles I think plus the pipeline flush). I invested in a great book about the ARM that goes into a lot of detail about this (I don't have it handy and forget the title, I can look at it later if you'd like). It's not cheap though.

The best book I know for this is ARM System Developer's Guide : Designing and Optimizing System Software; it really contains everything you need to develop hardcore ARM stuff for this platform and in general. Anyway, cycle-counting wise it's almost exactly as Dzz says; uncached loads will stall until the value is loaded (exact latency depends on memory speed, obviously), a cached load will have a one cycle latency unless it's a byte, half-word (16-bits on an ARM), or signed load in which case it'll take another cycle to sign-extend or clear it. Using the value from a load in the next instruction will add extra cycles into the pipeline to interlock. Branches are indeed 3 cycles plus a pipeline flush.

The other ones to be wary of:

a register-based shift (e.g. mov r0,r0,lsr r1) costs an extra cycle.

a multiply instruction's timing is based on the value in the right hand register, so if you know that r1 is always > r0 then do mul r2,r1,r0 and not mul r2,r0,r1 (actual penalty depends on the where the first bit that differs from the sign bit is). I can dump a table here if it's not in the ARM9 manual.

And the corollary, optimisations:

a 32x32=64-bit multiply costs only one more cycle than a 32x32=32-bit multiply
an MLA / xMLAL costs the same as a MUL / xMULL

For those of us that are used to StrongARMs (and XScale), the ARM9 implementation is quite clean regarding penalties. :) The only problem is that existing heavily StrongARM/XScale optimised stuff is not optimised for the ARM9. Ho hum.


Thats very intresting about the branch, may have to ask for that book for my birthday. I'm thinking of unrolling my scan line renderer inner loop into a long line of mov's to memory. Then add a value to the PC so that it starts at the correct place and that the last instruction will wright the last pixel in the scanline. Seing a branch is three cycles this should be a big boost to the usual 'begin_loop; write pixel; of_more_branch_to_loop_begin' Saying that I may just crash and burn. ;)
 
Last edited by a moderator:
I would like to know the name of that book, Dzz..
It's the same book that refractor mentioned, as it turns out! ARM System Developer's Guide : Designing and Optimizing System Software



Thats very intresting about the branch, may have to ask for that book for my birthday. I'm thinking of unrolling my scan line renderer inner loop into a long line of mov's to memory. Then add a value to the PC so that it starts at the correct place and that the last instruction will wright the last pixel in the scanline. Seing a branch is three cycles this should be a big boost to the usual 'begin_loop; write pixel; of_more_branch_to_loop_begin' Saying that I may just crash and burn. ;)
That's a pretty clever idea... make a long sequence for the longest possible scanline then jump to whatever section will give you the correct number of pixels... as long as the processing for each pixel is the same, it should work great!
 
Last edited by a moderator:
Then add a value to the PC so that it starts at the correct place and that the last instruction will wright the last pixel in the scanline. Seing a branch is three cycles this should be a big boost to the usual 'begin_loop; write pixel; of_more_branch_to_loop_begin' Saying that I may just crash and burn. ;)
What you're talking about is called a branch table. The problem there is that unrolling burns through the cache really quickly on these CPUs with small caches. While branching does cost you 3 cycles, evicting/loading a cache-line costs you a lot more. If you're doing something that blasts lines to the screen as fast as possible then it's a reasonable strategy... if that's all you're going to do. (i.e. for a fully unrolled line algo you'll need something like 4 instructions per pixel for 320 pixels = 1280x4 = 5KB+setup for the plot code).

Personally I wouldn't worry too much about loop unrolling. 3 cycles isn't too bad in the grand scheme of things and conditional execution helps avoid branching code anyway. As always, benchmarking is the only true way to tell. :)

(Edited 'cos I was an idiot once again)
 
Last edited by a moderator:
I would like to know the name of that book, Dzz..
It's the same book that refractor mentioned, as it turns out! ARM System Developer's Guide : Designing and Optimizing System Software



Thats very intresting about the branch, may have to ask for that book for my birthday. I'm thinking of unrolling my scan line renderer inner loop into a long line of mov's to memory. Then add a value to the PC so that it starts at the correct place and that the last instruction will wright the last pixel in the scanline. Seing a branch is three cycles this should be a big boost to the usual 'begin_loop; write pixel; of_more_branch_to_loop_begin' Saying that I may just crash and burn. ;)
That's a pretty clever idea... make a long sequence for the longest possible scanline then jump to whatever section will give you the correct number of pixels... as long as the processing for each pixel is the same, it should work great!

Just tried it, worked a treat. I'm now breaking the protection unit. I'm making all the upper 32 megs cached with a protection hole over that 'landmine' at 16meg so I'll know if I tread on it. So may be a while till its running again.


Then add a value to the PC so that it starts at the correct place and that the last instruction will wright the last pixel in the scanline. Seing a branch is three cycles this should be a big boost to the usual 'begin_loop; write pixel; of_more_branch_to_loop_begin' Saying that I may just crash and burn. ;)
What you're talking about is called a branch table. The problem there is that unrolling burns through the cache really quickly on these CPUs with small caches. While branching does cost you 3 cycles, evicting/loading a cache-line costs you a lot more. If you're doing something that blasts lines to the screen as fast as possible then it's a reasonable strategy... if that's all you're going to do. (i.e. for a fully unrolled line algo you'll need something like 4 instructions per pixel for 320 pixels = 1280x4 = 5KB+setup for the plot code).

Personally I wouldn't worry too much about loop unrolling. 3 cycles isn't too bad in the grand scheme of things and conditional execution helps avoid branching code anyway. As always, benchmarking is the only true way to tell. :)

(Edited 'cos I was an idiot once again)

As my raster code is 'very' basic its just one instruction per pixel. So its 320x4 bytes of instruction cache thingy.
I would have expected that maybe a stall on the first few instructions whilst the cache catches up. But then as I render the tri these instructions are more likely be in the cache. The 3 cycle branch is a saving per pixel so
it quickly adds up as a big saving. One more optimisation is to do a preamble to get the dest dword aligned then wright with this jump method four pixels per instruction dropping out at the end to do the tail of odd bytes if needed. (i'm running palatised)
 
Last edited by a moderator:
uncached loads will stall until the value is loaded (exact latency depends on memory speed, obviously), a cached load will have a one cycle latency unless it's a byte, half-word (16-bits on an ARM), or signed load in which case it'll take another cycle to sign-extend or clear it. Using the value from a load in the next instruction will add extra cycles into the pipeline to interlock. Branches are indeed 3 cycles plus a pipeline flush.

That's what I wanted to know.
Initially, I wanted to use waiting cycles after the load/write of an uncached memory location to do all other computations, but it does not work like that (as it's slower than expected). Forcing me to optimize further ;)

Thank you.

For those of us that are used to StrongARMs (and XScale), the ARM9 implementation is quite clean regarding penalties. :) The only problem is that existing heavily StrongARM/XScale optimised stuff is not optimised for the ARM9. Ho hum.

Too bad there is no instruction on ARM9 to preload a value, like there is in strongARM (or XScale, I dont know)
 
Last edited by a moderator:
Back
Top