gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

ASM > General advices on code organisation

#6018 - funkeejeffou - Thu May 15, 2003 9:54 am

I've been coding for a week now on asm and i can get some graphics routines running fine now, but independantly(a routine for bresenham, another to draw an image...).
Since I'm new to asm and that I've been mostly coding in C, some questions went to me :
- how do we represent negative numbers?
- how do we create variables?
- how do we switch the proc to thumb or arm mode?
- how do we place our variables in iwram, ewram(ie is there an equivalent to malloc)?
- how do we place code in iwram, ewram?
- how can you represent the equivalent of a C structure in asm?
- what are pools?
- do you use an asm editor(wich one?)
- what is the general structure and organisation of a large program?
I know it is a lot to answer, but I've been browsing the asm tutorials as well as the arm docs, and I still can't answer precisely those.
If someone could please just guide me, I'm actually passing a 3D engine from c to asm and it is not so simple :)

Thanks to anyone who will answer me.

PS : I'm using goldroad 1.7

#6025 - DekuTree64 - Thu May 15, 2003 4:57 pm

- how do we represent negative numbers?
It's called two's compliment. It means the number looks the same as positive, except the bits are opposites, except you have ato add one to the bitwise-opposite to get the negative. -1 (in 8-bit binary) would be 11111111, because if you add 1 to that, it overflows and becomes 0. Likewise, -5 would be 11111011, because +5 is 101, so invert that and you get 11111010, but if you add 101 to that, you get 11111111, which is-1, not 0.
I hope that made some sort of sense^^;
- how do we create variables?
Use the stack pointer. It's called sp, or r13. Just different names for the same register. It's full-descending, so the word it currently points to is considered used, so don't overwrite it. And decending means it tharts at the top and grows down, so like to declare 2 4-byte of variables, you'd use sub r13, r13, #8, and then use str/ldr rWhatever, [r13], and str/ldr rWhatever, [r13, #4] to access them. You have to keep track of how many things you've pushed onto the stack at the current point in your code.
Global vars are accessed by labels though, so use like
ldr rWhatever, =gVar //the address of the variable
and then
ldr rWhatever. [rWhatever]
to get the value in it. I've never actually created a global var in ASM though, but I think it would be done like
.section .iwram (or ewram)
.global v1
v1:
.word 0 (the initial value)
.global v2
v2:
.word 1 (initialize to 1)
But keep in mind that's ust guessing, so don't blame me if it doesn't work^^
- how do we switch the proc to thumb or arm mode?
At the start of the functin, use .ARM or .THUMB to tell it which instruction set the following code uses. When calling thumb functions, use ldr rWhatever, =address
orr rWhatever, rWhatever, #1
bx rWhatever
The lower bit of the address you bx to tells it which instruction set to use.
- how do we place our variables in iwram, ewram(ie is there an equivalent to malloc)?
The stack is in IWRAM, and EWRAM is free to do whatever you want with, though I'm pretty sure you can use .section .ewram to put global vars there the same as IWRAM. I treat is like a giant 256K stack, and I have a memory manager to claim a little of it for allocating little bits at a time when needed.
- how do we place code in iwram, ewram?
.section .iwram (or .ewram) at the start of the function
- how can you represent the equivalent of a C structure in asm?
As far as I know, you just use an array-type thing and keep track of the member offsets yourself
- what are pools?
That's where literals are stored, like for accessing global vars. At the end of your function, put
.pool
gVar1:
.word gVar1
gVar2:
.word gVar2
then ldr rWhatever, =gVar1 loads the word at that label there. The linker replaces the names with the actual addresses of the variables.
Actually you don't need to do that explicitly, the assembler does it for you. You would need to do it yourself though if you have a long function so the pool at the end is out of range of ldr's immediate value range at the start of the function. Then you could put the pool right in the middle, and just branch over it when your code gets there.
I wouldn't swear to anything I just said though, anybody want to verify it?
- do you use an asm editor(wich one?)
I use MSVC++, cause it has automatic indenting, but you could use notepad or anything else.
- what is the general structure and organisation of a large program?
Never written a whole program in ASM.

Hope hat helps, I was half guessing some of the time, but I'm pretty familiar with the ways of ARM ASM though, so it's probably all at least close to right^^

#6036 - torne - Thu May 15, 2003 6:49 pm

Mostly right. Both I and the previous poster assume GNU as, rather than GoldRoad.

Quote:
- how do we represent negative numbers?

In the assembler, just write negative numbers. The assembler will generate the complements for you, and/or use the MVN instruction (move not) to invert numbers. But they are stored as two's complement, exactly the same as in C =)

Quote:
- how do we create variables?

Local variables do indeed work the way DekuTree explains. To make a global variable that's initialised to a value, do it the way DekuTree says; to make one that's initialised to zero and doesn't occupy any space in your ROM, use:
Code:
.bss
.global var1
var1:
.skip <number of bytes you want>


I recommend not using labels like v1, btw, as they conflict with the ATPCS register naming conventions (a1-a4,v1-v8,fp,sp,ip,lr,pc and probably one or two more I missed)

Quote:
- how do we switch the proc to thumb or arm mode?


You never need to explicitly switch the processor. Use .ARM and .THUMB to mark sections of code as being in one or the other. Do not do what DekuTree suggests to call functions, I'm afraid. To call functions by label, just call them normally:
Code:
.arm
.b thumbfunction

and the assembler will automatically patch the call site to make the switch between CPU modes if neccecary (a dummy 6 byte shim function will be inserted elsewhere in the code to transition between cpu states).

To call functions through a function pointer, use bx, but rather than setting the bottom bit to one yourself, declare the pointer as being a thumb function:
Code:
.thumb_func
AFunctionWrittenInThumb:
... instructions

as the assembler will then automatically add the 1 to the value of the symbol AFunctionWrittenInThumb (the .thumb_func directive also automatically switches you to Thumb asm mode).

When you return from a function in a program using both ARM and Thumb instructions (this is called interworking), use bx lr, not mov pc, lr. If you use ldmfd or pop to return from functions by popping the saved LR value directly into the PC, this is also OK as setting the PC via a load is also guarenteed to restore the processor state.

For more information on interworking, read the ATPCS, the ARM/Thumb Procedure Call Standard, available from ARM's site.

Quote:
- how can you represent the equivalent of a C structure in asm?
As far as I know, you just use an array-type thing and keep track of the member offsets yourself


There is a section reserved for making structures:
Code:
.struct 0
Field1:
.struct Field1 + 4
Field2:
.struct Field2 + 2
Field3:
.struct Field3 + 2
StructEnd:


This creates a structure template 8 bytes long, containing a four-byte field and two two-byte fields. You access the members like this:
Code:
ldr r1, [r2, #Field1]   @ Loads Field 1 from the structure pointed to by r2


Quote:
- what are pools?

Never explicitly put anything into a pool. The .pool directive is not for designating the start of a pool, it is to cause the assembler to dump its pending pool constants at the current location. You should not normally need to put in any pool directives as the pool is automatically dumped at the end of each file; if the assembler complains that pool offsets are too large, you may need to designate a pool near to the middle of your file, usually between functions such that there is no need to branch over it. If you have a truly titanic function which is bigger than the offsets supported by the ARM instruction set, then your function probably needs to be split up anyway. =)

Quote:
- do you use an asm editor(wich one?)


I use gVim, its syntax highlighting supports GNU as quite happily.

Quote:
- what is the general structure and organisation of a large program?


The same as in C, usually. Place related functions in the same source file, along with their global variable definitions. You don't need any header files in asm except for the preprocessor, if you use it. You will need headers with extern declarations for your asm functions if you intend to call them from C, however.

I'm writing a complete operating system for the GBA entirely in assembler, mostly in Thumb for fast execution directly from the cart. I have studied all the ARM reference manuals, including the ones you don't get from their site (google for the ARM ARM, Architectural Reference Manual, for canonical info on the ARM/Thumb instruction sets, as the info in no$gba and friends' documentation is not perfect and is rather brief), and made up a lot of nice tricks. ASM is fun, especially on RISC.

Hope this helps,
Torne

#6119 - funkeejeffou - Sat May 17, 2003 2:31 pm

Thanks a lot you too for your replies, i can now visualise a little bit more how am i gonna code the engine.
Just some other questions and i'll stop bother you :

When i want to test if a 32bits variable is negative, should i check if it is more than 0x80000000 (ie 2power31), or can the assembler figure it out itself if i write
"cmp r0, #0"
"ble loop"

Also, if i want to use local variables, i've seen that i must declare them within a "textarea":
@textarea 0x300000(Ram adress)
var1 ....
var2 ....
@endarea
the problem is that if in another functiun i have new local variables, defining this :
@textarea 0x300000(Ram adress)
var3 ....
var4 ....
@endarea
would make var3 equivalent to var1 as well as vr4 equivalent to var2
Should I code a functiun equivalent to malloc so that i keep trace of how much memory i have still left in each type of ram?

What does bx exactly do? Does it make the jump to the label and set the end bit to its inverse value(set to 1 if 0, set to 0 if 1), so that the proc is switched to thumb or arm?
Still can't figure out how to switch the CPU mode, putting
"@arm" or "@thumb", doesn't seem to be enough, and "@thumb" generates compilation errors(don't know why as the code compiles in arm mode...)

Thanks again Torne and Dekuthree64 for your attention, hope you'll help me again.
PS : Are you too coding in GNU ASM, your not using the goldroad syntax (Goldroad doesn't have ".section" stuff, right?)

#6134 - torne - Sat May 17, 2003 11:23 pm

To test if a value is negative, you can either compare it to zero and use the 'le' condition, as you said, or, since testing for zero is a 'special case', you can do something like the following:
Code:
subs r1,r1,#1    @ subtract 1 from r1 and also set flags
ble loop

If you append 's' to almost any ARM arithmetic instruction (doesn't work in thumb), it will set the CPU flags in the same way as if you compared the result to zero, thus saving you using a cmp instruction at all.
the 'le' test is signed, meaning that 0x90000000 is less than zero. If you want unsigned comparisons (which you don't here, but might elsewhere) then use 'mi' (minus) and 'pl' (plus or equal): under 'pl, 0x90000000 is greater than zero.

Declaring variables like that is declaring a *global* variable, not local. Local variables live on the stack, and are not at any defined address, only an address relative to the stack pointer. I suggest you consult the web and find some references to stack organisation and C local variable implementation; as it's a lil complex to explain here if you don't know about that.

You can use malloc in dka, don't know about goldroad; dka's c library contains a malloc implementation. But you shouldn't malloc to store local variables, usually.

bx does not jump to a label; bx jumps to the value of a register. bx uses the low bit of that register to determine whether it should be switching mode or not; if the low bit is 1, it switches to (if it's not already in) Thumb mode, andd if it's 0, to ARM mode. Any symbol that you intend to use as a Thumb function pointer must be declared with GoldRoad's equivalent of .thumb_func as I said in my previous post, else its low bit won't get set for you.

You can't just declare a section of code as being in Thumb; Thumb instructions have a different format. Arithmetic instructions take only two arguments on the whole (the destination is implicitly the first argument), only branches can be conditional, and a whole swathe of other restrictions. Thumb is a subset of ARM and you need to learn what you can and can't do in it.

I used goldroad briefly and don't like it personally; I develop for other platforms using GNU binutils, so am used to its more 'standard' syntax; plus Goldroad seems to take a little too much into its own hands for my liking; I like to know where every byte of my ELF binaries came from. =)

Torne