jmetal88 posted on Jul 23 2006 at 08:56 AM said:
BTW, I always did want to learn assembly, but the huge lists of instructions and the time it would take to learn always put me off. Plus, I never could figure out which instructions I would need to use together to get a certain output, so if you guys have any links to documents that would help me with ARM assembler specifically on the GP2X, I would appriciate it.
Nonsense, once you learn assembler, all other languages are just syntax. All processors are pretty much the same, some registers, move or load/store (or both), branching, add, subtract. Sometimes multiply and divide.
Personally I start learning an ISA (instruction set architecture, assembly language) by writing a disassembler. Partly because some of the demo versions of compilers do not offer a disassembler (I guess they dont want you to know just how bad their compiler is until after you have paid for it). So new widget, new compiler, new assembler but no example assembly code. So I write a disassembler. But that is just me, with gcc and arm and the gp2x that is not required, unless you want to do it for fun (I find it fun). For example with the GPH tools you can write this program
int ra,rb,rc;
int main ( void )
{
ra=rb+rc;
return(0);
}
Build it with:
arm-linux-gcc -o test.elf test.c
Then disassemble it with
arm-linux-objdump -D test.elf > test.txt
Now gcc tacks on TONS of baggage, one of my dislikes of the BIG compilers like gcc. Anyway, search for <main> and eventually you find what you are looking for:
00008478 <main>:
8478: e1a0c00d mov ip, sp
847c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
8480: e24cb004 sub fp, ip, #4 ; 0x4
8484: e59f3020 ldr r3, [pc, #32] ; 84ac <.text+0x140>
8488: e5932000 ldr r2, [r3]
848c: e59f301c ldr r3, [pc, #28] ; 84b0 <.text+0x144>
8490: e5933000 ldr r3, [r3]
8494: e0822003 add r2, r2, r3
8498: e59f3014 ldr r3, [pc, #20] ; 84b4 <.text+0x148>
849c: e5832000 str r2, [r3]
84a0: e3a03000 mov r3, #0 ; 0x0
84a4: e1a00003 mov r0, r3
84a8: e89da800 ldmia sp, {fp, sp, pc}
84ac: 000106d8 ldreqd r0, [r1], -r8
84b0: 000106d4 ldreqd r0, [r1], -r4
84b4: 000106dc ldreqd r0, [r1], -ip
Okay, sorry, try it this way
arm-linux-gcc -o test.elf -O3 test.c
then objdump then
00008478 <main>:
8478: e59f301c ldr r3, [pc, #28] ; 849c <.text+0x130>
847c: e59f201c ldr r2, [pc, #28] ; 84a0 <.text+0x134>
8480: e5931000 ldr r1, [r3]
8484: e5923000 ldr r3, [r2]
8488: e59f2014 ldr r2, [pc, #20] ; 84a4 <.text+0x138>
848c: e0811003 add r1, r1, r3
8490: e3a00000 mov r0, #0 ; 0x0
8494: e5821000 str r1, [r2]
8498: e12fff1e bx lr
849c: 000106c8 andeq r0, r1, r8, asr #13
84a0: 000106c4 andeq r0, r1, r4, asr #13
84a4: 000106cc andeq r0, r1, ip, asr #13
The first instruction is trying to load rb into register r3, it is a bit of a double indirect. They take r15 the program counter (pc) add an offset to it which gives the address 849c which you can see in the disassembly
(note when you use the pc it is two instructions 8 bytes for arm 4 bytes for thumb, ahead of the instruction you are using it with so 0x8478+8+28 = 0x849c. This is because of the pipeline). Then you see 0x849c and that has an address in it r3, is only getting the address of variable rb not actually the value...yet.
000106c4 <rc>:
106c4: 00000000 andeq r0, r0, r0
000106c8 <rb>:
106c8: 00000000 andeq r0, r0, r0
000106cc <ra>:
106cc: 00000000 andeq r0, r0, r0
r2 gets the address of rc
then they go another level of indirection and
r1 gets whatever is at the address stored in r3, which is the C variable rb
now r3 is free to be used again so r3 gets whatever r2 points at which is the variable rc
Instead of doing the add they now prep for the storage of the answer, some optimizer thing I assume, and r2 points at the location to store the answer, variable ra
Then they do the add
r1=r1+r3
Which is our line of c code basically ra=rb+rc
R0 gets the return value for the main() function because I wrote return(0); at the end of the function (again out of order for as of yet no apparent reason).
Then finally store the answer r1 to the memory containing the variable ra.
Then a bx lr. Read up on your branch instructions. Bx is a complicated way to do it but they do it because this is an arm/thumb compiler. if it were arm only they would normally just use mov pc,lr and you can just use mov pc,lr if you like. Note, thumb as fun as arm, but really only shines on 16 bit data/memory bus systems like the GBA, no need to add this complication right now in your life.
pc is r15, the program counter. lr is r14, when a branch happens the return address is stored in r14. sp is r13. I assume ip is r12, not sure. I think you have to go way back to understand these abbreviations, just know that gccs disassembler likes to use them, if you look the instruction up in your ARM ARM you can figure out the above, pc is r15, lr is the link register or r14, etc.
Most compilers let you mess with or destroy the contents of r0-r3 in a funtion, but if you are going to touch r4 on up you need to save them, typically on the stack.
stmdb r13!,{r4,r5,lr} is an example of a push
ldmia sp!,{r4,r5,lr} is an example of a pop
Some compilers will push lr on the stack and the pop it off into r15 to or will do the ldmia then mov pc,lr or bx lr. It varies widely from compiler to compiler and one assembly writer to another.
If you are going to call a function within a function you have to save r14 on the stack as it will get destroyed with your bl instruction.
int myfun( int x )
{
return(x+rc);
}
int main ( void )
{
ra=myfun(rb);
}
Okay that was too much, sorry. Trying to encourage you not scare you. For fun change one of them to an unsigned short instead of an int, see what happens. Sometimes using unsigned chars and unsigned shorts actually cost you extra instructions
For example
int ra;
unsigned short rb,rc;
int main ( void )
{
rb+=rc;
ra=rb;
return(0);
}
I am doing good right? rb and rc may only need to count to say 1000 decimal, so why waste the bytes to store them in a 32 bit value? That should save memory and be faster right? For speed and efficiency thats why:
00008478 <main>:
8478: e59f0028 ldr r0, [pc, #40] ; 84a8 <.text+0x13c>
847c: e59f2028 ldr r2, [pc, #40] ; 84ac <.text+0x140>
8480: e1d030b0 ldrh r3, [r0]
8484: e1d210b0 ldrh r1, [r2]
8488: e59f2020 ldr r2, [pc, #32] ; 84b0 <.text+0x144>
848c: e0833001 add r3, r3, r1
8490: e1a03803 mov r3, r3, lsl #16
8494: e1a03823 mov r3, r3, lsr #16
8498: e1c030b0 strh r3, [r0]
849c: e3a00000 mov r0, #0 ; 0x0
84a0: e5823000 str r3, [r2]
84a4: e12fff1e bx lr
84a8: 000106d2 ldreqd r0, [r1], -r2
84ac: 000106d0 ldreqd r0, [r1], -r0
84b0: 000106d4 ldreqd r0, [r1], -r4
The mov r3,r3,lsl #16 then the lsr. I am trying to store a 16 bit unsigned value in a 32 bit signed value, so it has to convert from unsigned 16 to signed 32, it does this with these two instructions. but is it always faster? You have to balance the time it takes to load and store the two extra bytes every time you access those variables, vs the two extra instructions, twice as many bytes plus extra cycles for execution, but perhaps the cache covers the instructions speed better than the loads and stores would to possibly uncached ram.
Learning assembler is one thing, watching what compilers do is the next step. And as Dzz stated and if you search for my memcpy experiment on this board, you will find, at least in the embedded world you have to know assembler enough to undo compilerisms. You may know that it is okay to store rb+rc in ra as is without controlling the upper bits, but the compiler doesnt the compiler is doing what you told it to do not what you wanted it to do...You will also find that it makes a HUGE difference what compile options, what version of gcc or other brand compiler, what options were chosen when gcc was itself compiled, etc. No two gccs or any two compilers are created equal, they are vastly different even if they carry the same name and version. Again see my memcpy info somewhere on this board, I know that it is faster to move 4 bytes (32 bits) on a 32 bit data bus at the same time instead of 8 bits at a time taking 4 times longer. Can you tell me if the compiler (suite) you are using knows this? How would you figure it out. Just having been bit by this yesterday, how do you know if the memory you are copying to/from is word aligned? Does this architecture allow for unaligned transfers, if so what is the cost/consequence? arm actually does allow unaligned transfers, and despite what the ARM ARM says it is deterministic, trust me, but ugly you dont want to do it, you cannot do 32 bit moves easily to unaligned addresses, you would have to go halfword or byte until you get to an aligned address then go aligned for speed, then patch up the odd bytes at the end. or take control of your compiler.
Must haves:
http://rts-lab.eas.asu.edu/NSF_EI/courses/...PSD1/cse421.htm
You will find the ARM ARM here, the ARM Architecture Reference Manual. this is rev E, AFAIK this is the latest rev and matches the book that you find in print everywhere (this is a free copy of that book, also available from arm if you request a technical information cd).
This has the assembly language definitions as well as the opcode bits, etc in case you want to do a dissassembler (and if you do, remember to compare your results to a hopefully known good disassembler as the arm arm, all revs, have mistakes).
Use this book to figure out what the disassembled C programs are doing.
You will at a minimum want the 920T and 940T technical reference manuals from this page:
http://www.arm.com/documentation/ARMProcessor_Cores/
If nothing else they describe the MMU and MPU (yet another reason why I like the 940t, it is simpler).
The gp2x has one of each of these cores.
http://www.mesdigital.com
Is the home of the system on a chip used in the gp2x, you will need the manual for it, it has all of the peripherals, uart, video stuff, timer stuff, etc. Using this and looking at the sdk2x or rlyeh's code you can figure out what they are doing and tweak it or change it or learn from it to do your own thing. Faster, better cheaper.
I see a data sheet here but not the manual.
This link:
http://wiki.gp2x.org/wiki/Docs_and_Papers
Has the MMSP2 manual as well as the 940t and 920t documents it appears.
Absolutely nothing to be afraid of here, there are plenty of examples between rlyeh and the HH sdk (sdk2x) and the kernel source (only a few files of interest there most are generic linux) as well as disassembly of C modules to help you understand how to write assembler modules that link into C.
ARM is a pretty good architecture to cut your assembly teeth on. I learned on the PDP-11 (no I am not that old, when I learned it the machines I learned on were antiques in the corner of the cs lab), which is by far the best ISA to learn assembler from. ARM is the leading processor on the planet so if you are going to learn anything, learn the ARM. its definitely easier to learn and use than an x86, trust me.
Eventually you will want to dabble in building a gcc cross compiler from sources, if you are learning assembler to take control of your C programs then you need to take control of your c compiler to take control of your c programs. It is much easier to build your own gcc, than to, for example try to get GPH's gcc to build good 940t binaries for example.
And you have come to the right place there are a number of good low level programmers on this board, all of which have dabbled in what you are considering doing...
And if you really get the assembly bug and want to learn more I can point you to a number of hardware demo/eval widgets for $20 or less from which you can learn 8051, or msp430 or Atmel AVR.
Also, even though old, the Zen of assembly language by Michael Abrash is an eye opener for the kind of stuff I/we are talking about here. I am fortunate enough to have an actual print copy, which is rare. It was later tacked on/into his monster zen of graphics programming book which I think was later open sourced and available for download. it is 8088/8086 which is like greek until you have had some assembly experience. But the concepts are very true today. For example if you want to do a bit blit like Dzz is talking about, is it better to do horizontal rows as groups because the memory addresses are sequential and you can save instructions and use the write buffer? Or are vertical columns better, you can still take advantage of the instruction set for speed? Abrashes rule of just test it, found that a number of times, what you thought might be fastest, wasnt, later you might figure out that is because of the cache or the write buffer or how the peripheral itself works. I think he is now essentially retired in place at Microsoft (a "graphics fellow" probably) as you dont hear much from him anymore, wow, maybe you do, he is still out there kickin it.
http://en.wikipedia.org/wiki/Michael_Abrash
Wow, I can never write a short email/note. Bottom line, I think the only reason these days for learning assembler is to take control of the compiler, generally for performance reasons. If you are not interested in performance, or are happy with C or C++ or Java or whatever high language. Then no assembly is required.
David
p.s. ISA's are not huge, even if their books are. its just a few registers, loads, stores, branches and a few math functions. Rember everything you need to know about a computer boils down to:
ONE, ZERO, AND, OR and NOT.
Everything in the computer is ones and zeros connected by and, or and not gates.