Jumping into ARM assembly


Thx all. I get a better picture.


It's been three days I try to now display as binary.


r1 is the number


I increase r2 as 1 2 4 8 16 32 until it's > r1, then come back to the previous r2 value with a shift. This part works.


Then do a XOR between r1 and r2.


If r1 >= r2 display a 1


If r1 < r2 display a 0


then r1 = r1-r2


Shift r2 to a lower ^2


Compare again, execution conditions etc.


I tried different ways without success. Maybe the idea is wrong ?
 
That algorithm seems basically right.  You're using the side effect of an XOR to see if it flipped the bit from a 0 to a 1 or a 1 to a 0 - in the first case the result will be greater than r1, and in the second the opposite.  I've not seen that before, but it should work, and is only one instruction slower than the more obvious (to me) compare r1 vs r2, emit 1 if GE, 0 if LT, with no XOR step.


How is the code failing?  Does it not complete, and does it print anything?
 
Huh ! Now that you mention it, I don't know why the XOR came in my mind !


Well the code prints 1 and 0's, but not the right sequence, I'll give another shot.
 
You could also ANDS r1 and r2 then print 0 if ZE and 1 if NZ, if you'd like.  You could then avoid even having to remove r2 from r1, as that operates as a bit tester, and isn't influenced by higher or lower bits.  I've not written it out, but I think that might be the shortest way of doing this, and the quickest without trading memory for speed.
 
I'll check that.


So here's the current non optimized code, which displays a number as binary :

Code:
        .text
        .align 2

        .macro syswrite         @ print what r1 points at, must be ascii, buffer length r2 must be set
        push {r0, r7}
        mov r7, #4              @ system call number, 4 is 'write'
        mov r0, #1              @ file descriptor 1 - stdout
        svc 0                   @ call the Linux kernel and print the ascii text
        pop {r0, r7}
        .endm

        .macro newline
        push {r1}
        ldr r1, =newline        @ point on the buffer to write, \n, a newline
        mov r2, #1              @ newline buffer length
        syswrite
        pop {r1}
        .endm

        .macro zero
        push {r1, r2, r11}
        ldr r1, =asciicounter   @ point on =asciicounter
        add r11, r9, #48        @ convert the digit counter to ascii
        str r11, [r1]           @ store it to be displayed
        mov r2, #1              @ buffer length
        syswrite
        pop {r1, r2, r11}
        .endm

        .macro one
        push {r1, r2, r11}
        ldr r1, =asciicounter   @ point on =asciicounter
        add r11, r10, #48       @ convert the digit counter to ascii
        str r11, [r1]           @ store it to be displayed
        mov r2, #1              @ buffer length
        syswrite
        pop {r1, r2, r11}
        .endm


        .global _start
_start:

        mov r5, #12             @ set the number
        mov r8, #1              @ set the "cursor"
        mov r9, #0              @ constant 0
        mov r10, #1             @ constant 1
_loop1:

        mov r8, r8, lsl #1      @ r8 * 2
        cmp r8, r5              @ increases to know when the number is < x^2
        ble _loop1
_loop2:

        mov r8, r8, lsr #1      @ r8 / 2
        cmp r8, #0
        beq _end                @ cursor at 0 -> end

        cmp r5, r8              @ display a 0 or a 1
        blt _zero
        bge _one
        b _loop2
_zero:

        zero
        b _loop2
_one:


        one
        sub r5, r8              @ remove the msb for the next comparison
        b _loop2
_end:

        newline                 @ the binary number is now assembled

	mov r7, #1              @ exit
	svc 0                   @ call the Linux kernel and exit

        .data
asciicounter:                   .word 0                 @ must be initialized
newline:                        .ascii "\n"             @ \n is a newline
div_10_reciprocal:              .word 429496730         @ ceil((2^32)/10)


 


 


Any opinion about this option ?


 


.syntax [unified | divided]


This directive sets the Instruction Set Syntax as described in the ARM-Instruction-Set section.
 
I see no benefit for you of using unified syntax.  It would mean you could write immediates as '24' instead of '#24', but in exchange you'd need to change all your CMP instructions to be CMPS.  All of the other changes relate to THUMB and ARM64 instructions, and so far you've stuck to classic ARM32 instructions alone.


Regarding the code you posted, it looks good to me.  The CMP R8, #0 is unnecessary - any comparison with #0 can be eliminated in ARM32 code.  You just need to add the S flag to the previous MOV statement.  You might need to change the BEQ to a BZE, but to be honest I haven't refreshed my knowledge of the two letter conditionals for a while, and EQ might still work fine there.


The push and pop instructions inside your macros are wasteful, but I guess you're just seeing how they work there, so that's not a problem.


I'm not personally a fan of the way you've divided up the setting of the r9 and r10 constants, and then having two macros to print them out.  It'd be better to just have one to print out asc(48) and one to print out asc(49) IMO.  Or make them constant strings if you must.  Your solution only took a little bit of scrolling up and down to grok properly though, so it's not heinous, I'm just not a particular fan.


Out of interest, did you discover the mistake you were making before that made it print out the wrong result?


Edit: Oh, and that B _loop2 after the BGE and BLT - in some languages that'd be a sensible guard, but in machine code it's strictly an instruction that will never be executed.
 
Last edited by a moderator:
Yes, the macros are overkill ^^.


Here's the corrected code :

Code:
        .text
        .align 2

        .macro syswrite         @ print what r1 points at, must be ascii, buffer length r2 must be set
        push {r0, r7}
	mov r7, #4              @ system call number, 4 is 'write'
        mov r0, #1              @ file descriptor 1 - stdout
        svc 0                   @ call the Linux kernel and print the ascii text
        pop {r0, r7}
        .endm

        .macro newline
        push {r1}
	ldr r1, =newline        @ point on the buffer to write, \n, a newline
        mov r2, #1              @ newline buffer length
        syswrite
        pop {r1}
        .endm

        .macro zero
        push {r1, r2, r11}
        ldr r1, =asciicounter   @ point on =asciicounter
        add r11, r9, #48        @ convert the digit counter to ascii
        str r11, [r1]           @ store it to be displayed
        mov r2, #1              @ buffer length
        syswrite
        pop {r1, r2, r11}
	.endm

        .macro one
        push {r1, r2, r11}
        ldr r1, =asciicounter   @ point on =asciicounter
	add r11, r10, #48       @ convert the digit counter to ascii
        str r11, [r1]           @ store it to be displayed
        mov r2, #1              @ buffer length
        syswrite
        pop {r1, r2, r11}
	.endm


	.global _start
_start:

       	mov r5, #12             @ set the number
	mov r8, #1              @ set the "cursor"
	mov r9, #0              @ constant 0
	mov r10, #1             @ constant 1
_loop1:

       	mov r8, r8, lsl #1      @ r8 * 2
	cmp r8, r5              @ increases to know when the number is < x^2
	ble _loop1
_loop2:

       	movs r8, r8, lsr #1     @ r8 / 2
	beq _end                @ cursor at 0 -> end

	cmp r5, r8              @ display a 0 or a 1
	blt _zero
	bge _one
_zero:

      	zero
	b _loop2
_one:

        one
        sub r5, r8              @ remove the msb for the next comparison
        b _loop2
_end:

        newline                 @ the binary number is now assembled

        mov r7, #1              @ exit
        svc 0                   @ call the Linux kernel and exit

        .data
asciicounter:                   .word 0                 @ must be initialized
newline:                        .ascii "\n"             @ \n is a newline
div_10_reciprocal:              .word 429496730         @ ceil((2^32)/10)
 
Out of interest, did you discover the mistake you were making before that made it print out the wrong result?

I don't know where i got the XOR, but as you said it was useless, things became clearer.
 
In this code sequence:


blt _zero
bge _one
_zero:


you can leave out the blt _zero instruction and the end result is the same.


As for the macros zero and one:


Since the macros are the same except the source register (r9/10) you could make one macro with one parameter (the source register).


But a better solution would be to have one macro which prints the value of the register (which you already have) and set the register value to 0/1 before using the macro - something like this:

Code:
	cmp r5, r8              @ display a 0 or a 1
	movlt r9, #0
	movge r9, #1
	subge r5, r8            @ remove the msb for the next comparison
   	zero                    @ print the value of r9
	b _loop2
 
Indeed, I can remove the blt _zero !


I enhanced a bit the script some days ago. I'll check your second suggestion tomorrow.


Binary display :

Code:
        .text
        .align 2

        .macro syswrite         @ print what r1 points at, must be ascii, buffer length r2 must be set
        mov r7, #4              @ system call number, 4 is 'write'
        mov r0, #1              @ file descriptor 1 - stdout
        svc 0                   @ call the Linux kernel and print the ascii text
        .endm

        .macro newline
        ldr r1, =newline        @ point on the buffer to write, \n, a newline
        mov r2, #1              @ newline buffer length
        syswrite
        .endm

        .macro zero
        ldr r1, =asciizero      @ point on ascii 0
        mov r2, #1              @ buffer length
        syswrite
        .endm

        .macro one
        ldr r1, =asciione       @ point on ascii 1
        mov r2, #1              @ buffer length
        syswrite
        .endm

        .global _start

_start:
        mov r5, #12             @ set the number
        mov r8, #1              @ set the "cursor"

_getLength:
        mov r8, r8, lsl #1      @ r8 * 2
        cmp r8, r5              @ increases to know when the number is < x^2
        ble _getLength

_display:
        movs r8, r8, lsr #1     @ r8 / 2
        beq _end                @ cursor at 0 -> end

        cmp r5, r8              @ display a 0 or a 1
        bge _one

_zero:
        zero
        b _display

_one:
        one
        sub r5, r8              @ remove the msb for the next comparison
        b _display

_end:
        newline                 @ the binary number is now assembled

        mov r7, #1              @ exit
        svc 0                   @ call the Linux kernel and exit

        .data
asciicounter:                   .word 0                 @ must be initialized
asciizero:                      .ascii "0"
asciione:                       .ascii "1"
newline:                        .ascii "\n"             @ \n is a newline
div_10_reciprocal:              .word 429496730         @ ceil((2^32)/10)
 
And here's the code which displays as hexadecimal :
Code:
   .text
   .align 2

   .macro syswritechar     @ print what r1 points at, must be ascii
   push {r0, r2}
   mov r2, #1     @ buffer length
   mov r7, #4     @ system call number, 4 is 'write'
   mov r0, #1     @ file descriptor 1 - stdout
   svc 0       @ call the Linux kernel and print the ascii text
   pop {r0, r2}
   .endm

   .macro newline
   push {r1, r2}
   ldr r1, =newline   @ point on the buffer to write, \n, a newline
   syswritechar
   pop {r1, r2}
   .endm

   .global _start

_start:
   ldr r1, =asciicounter   @ point on =asciicounter
   ldr r2, =511     @ load the value to be displayed
   mov r3, #15     @ load a 0b1111 mask to isolate hex segments
   mov r4, #8     @ number of rotations

_loop:
   ror r2, #28     @ rotate left the value
   and r5, r2, r3     @ and "mask" on the higher hex segment, and so on
   cmp r5, #9     @ 0-9
   add r5, #48     @ then "ascii" it
   ble _num     @ and go for display
   add r5, #7     @ or A-F and increase the ascii number

_num:
   str r5, [r1]     @ display it
   syswritechar
   subs r4, #1     @ decrease the rotation counter
   bne _loop

_end:
   newline  @ the binary number is now assembled

   mov r7, #1  @ exit
   svc 0  @ call the Linux kernel and exit

   .data
asciicounter:   .word 0     @ must be initialized
newline:   .ascii "\n"   @ \n is a newline

---EDIT: mmm the tabulations went missing in action :/ . Any tips on the new editor's behavior ?
 
Last edited:
This line:
Code:
blt _num @ and go for display
should be:
Code:
ble _num @ and go for display
otherwise you can't display number 9.
 
Here's a new version of the binary display, using the ror technique :
Code:
  .text
  .align 2

  .macro syswritechar  @ print what r1 points at, must be ascii
  mov r7, #4  @ system call number, 4 is 'write'
  mov r2, #1  @ buffer length
  mov r0, #1  @ file descriptor 1 - stdout
  svc 0  @ call the Linux kernel and print the ascii char
  .endm

  .macro newline
  ldr r1, =newline  @ point on the buffer to write, \n, a newline
  syswritechar
  .endm

  .global _start

_start:
  ldr r3, =511  @ set the number
  mov r4, #1  @ load a 0b1 mask to isolate bit segments
  mov r5, #32  @ number of rotations

_loop:
  ror r3, #31  @ rotate left the value
  ands r1, r3, r4  @ and "mask" on the higher bit segment, and so on

  ldreq r1, =asciizero
  ldrne r1, =asciione
  syswritechar
  subs r5, #1  @ decrease the rotation counter
  bne _loop

_end:
  newline  @ the binary number is now assembled

  mov r7, #1  @ exit
  svc 0  @ call the Linux kernel and exit

  .data
asciizero:  .ascii "0"
asciione:  .ascii "1"
newline:  .ascii "\n"  @ \n is a newline
 
That ROR #31 took me a bit of grokking to understand, but I think it's fine. Because you're stopping after 32 bits regardless, it doesn't matter than there's a danger of the number being rotated round again, so there's no benefit in using an LSL #1 instead.

It took my a little while to figure out if you were rotating the test bit or the number to print. That would perhaps be clearer if you used a constant test bit (ANDS R1, R3, #1), which would mean you wouldn't need to use R4 at all.

Personally, I think I'd use a variable test bit, and keep the number to be printed constant. That way you preserve the number, in case you want to use it again later (although in your case, since you used ROR, you can probably just do that once more to get the number back!). You'd need to start with a test bit of 2^31, and keep shifting that right by 1 until it ==1.
 
Well, it's just a learning/demo case.

I'll now try to get input events, like d-pad.
I looked into tetet code, but I don't get the meaning of the needed parameters.
Any help on this ?
 
Yeah, feel free to ignore any of my suggestions - they're just what occur to me as I read your code.

I'd be interested to look at that tetet code too, but I can't find the thread any more since the forum move, nor any other resources documenting how to read the keyboard from ARM Linux assembler. Let me know if you find anything.

Edit: Never mind, I've found the source now on the repo. Rather than using a search engine, or the boards search, the repo takes me straight to it!

Edit2: Okay, I think I grok the getkey subroutine. It leaves ev_code in r0, then it just compares it to #107, for example, to see if you've pressed {B}. All the keycodes are defined in macro.mac (as well as the constants for calls like sys_read, which does the actual reading - but the arguments to that call are all in tetet.s).
 
Last edited:
I tried to isolate the input-related stuff , and came with this without understanding the meaning:

ev_size=0x10
ev_key=1 @keyboard
ev_press=1
evdata_len=ev_size*4
ev_value=0xc @ signed word
ev_code=0xa @ short

.fill evdata_len,1,0
evdata_ofs: .word evdata
fd_tbl_ofs: .word fd_tbl
fd_tbl: .ascii "keyp" @ 'keypad'
 
This all looks like valid code to me, but it's untested:

ev_size=16
evdata_len=ev_size*4

getkey:
mov r2,#evdata_len
ldr r1,evdata_ofs
@ Clear evdata to evdata_len of zeroes
@ Set r0 to 'keyp' i.e. 0x7079656b (assuming I've got my endianness correct)
mov r7, #3
svc 0 @ Do sys_read
cmp r0, #ev_size
bge getkey_ok
@ Set r0 to 'gpio' i.e. 0x6f697067
svc 0 @ Do sys_read again, this time on gpio signals??
cmp r0,#ev_size
@ if lt, mov r0, #0; return - no key pressed
getkey_ok:
@ Got a valid key event
ldrh r2, [r1, #8]
ldrh r0, [r1, #10]
ldr r3, [r1, #12]
@ if r2==1 (#ev_key) and r3==1 (#ev_press), return key code in r0, else mov r0, #0 and return

evdata_ofs: .word evdata

evdata:
.fill evdata_len,1,0

@I'm not sure you need to check the gpios. It's mostly code to clear evdata, call sys_read (r7==3), then check the results in evdata.
@In practice this has been presented as a subroutine you bl into, so the first thing it does is push everything it needs onto the stack apart from r0, and to return it pops it back, possibly clearing r0 to 0 first
@I'm also not clear how this assembler syntax sets PC offset addresses, but hopefully I've copied and pasted enough from the source code for this to still work.
 
Back
Top