Jumping into ARM assembly


So it's more a quick trick than a real operation.
I found a nice division explanation there, I will test it :
http://www.tofla.iconbar.com/tofla/arm/arm02/index.htm
To divide 50 (%110010) by 10 (%1010) in binary:
  • We shift 10 as far as we can (two places) to the left until it becomes %101000 and subtract this from %110010 to get the first digit.
  • %110010- %101000 = %1010 (First digit is 1)
  • Now we shift %101000 back one place to the right and try to subtract %10100 from what remains of the 25 we started off with.
  • %1010 - %10100 (Next digit is 0 - it 'won't go'!)
  • Shift right, get %1010 and try again:
  • %1010 - %1010 = %0 (Successful subtraction - next digit is 1)

Our '10' has now been shifted back two places to the right, returning it to its original value, which is our signal to stop and count up the digits in our answer - %101 in binary or '5' in decimal, w ich is of course the correct answer.
 
Yes, that's long division, done in pseudocode, as far as I can tell. I'd expect it to be pretty slow since it contains the instruction 'and try again' indicating it's iterative (albeit it will only iterate enough times to make the divisor bigger than the number to divide). It would do that iteration every time you needed to divide by 10, while umull is just one instruction (though I don't know off hand how quick it is).

The umull operation works because you're multiplying by 2**32/divisor, then dividing by 2**32 (because you're discarding the lower 32 bits), so you end up with the initial number divided by the divisor. But when 2**32 won't divide precisely, there's a slight error introduced. In fact, it will only divide precisely into itself, 1 and 2,4,8,16 etc. - it's only exact when you would be able to shift right to do the division.

Another possibility, since it's only ever the last two digits that seem to be wrong, you could calculate the first n-2 digits, then switch to long division for the last two. Since you're already working backwards that simplifies some things - start by dividing by 100 (or 10 twice), then enter your loop to store the other digits. As you're going keep an exponent multiplier going starting at 1000, then 10000 then 100000 and so on, which you can multiply by the number you've calculated for that digit, taking it away from a copy of the original number, so by the time you've run out of digits to print, what's left is the last two digits as a binary number. You could then do long division on it, or iteratively take 10s off of it, your choice, so get the last two digits printed (which you should do after you've pulled all the other digits off the stack in reverse order and printed them).

But given it took me a couple of attempts to write that paragraph out, it might not be the easiest concept to get into your head, I dunno.

Edit: Spelling corrections.

Edit2: Found the TRM; a UMULL is equivalent to three register-to-register simple instructions like SUB or CMP. Probably still quicker than long division though.
 
Last edited:
An accurate version of the display decimal.
I rewrote a division routine, slow but precise.
I also found that passing the registers to use to a macro/function could be pretty useful in order to track register usage in the main program.

Code:
.text
.align 2
.include "syscall.mac"
.include "math.mac"

.global _start

_start:
  mov r0, #0x80000000           @ set the number
  mov r11, #0                   @ set the digit counter

_loop:
  add r11, #1                   @ increment the digit counter
  cmp r0, #10                   @ compare the number with 10
  blo _end                      @ branch if unsigned < 10
 
  mov r1, #10
  slowdiv r0 r1 r2 r3
  push {r1}                     @ push the remainder on the stack
  b _loop                       @ back to loop

_end:
  push {r0}                     @ push the last digit, with the highest weight
  ldr r1, =asciicounter         @ point on =asciicounter
  mov r2, #1                    @ buffer length, to write one character, or a newline
  cmp r11, #10                  @ simple way to display the number of digits
  blo _onedigit                 @ with a length of 10 as a particular case

  mov r6, #49                   @ procedure to display 10
  str r6, [r1]                  @ store 1 to be displayed
  syscall sys_write
  mov r6, #48
  str r6, [r1]                  @ store 0 to be displayed
  b _endigit

_onedigit:
  add r6, r11, #48              @ convert the digit counter to ascii
  str r6, [r1]                  @ store it to be displayed

_endigit:
  syscall sys_write
  ldr r1, =asciinewline         @ point on the buffer to write, \n, a newline
  syscall sys_write

_reversed:
  pop {r8}                      @ display the number, beginning with the highest weight
  ldr r1, =asciicounter         @ point on =asciicounter
  mov r6, r8
  add r6, #48                   @ convert the number to ascii
  str r6, [r1]                  @ store it to be displayed
  syscall sys_write
  subs r11, #1                  @ the digit counter is now used for the number display loop
  bne _reversed

  ldr r1, =asciinewline         @ point on the buffer to write, \n, a newline
  syscall sys_write
 
  syscall sys_exit

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

math.mac :
Code:
.macro slowdiv rreg0 rreg1 rreg2 rreg3  @ rr0/rr1, rr2 rr3 tmp
                                        @ result in rr0, remainder in rr1
  cmp \rreg1, #0        @ check for divide by zero
  beq _divide_end
  cmp \rreg0, \rreg1    @ check for x/x=1
  beq _divide_same

  mov \rreg2, \rreg0    @ re-arrange registers to later get results in rr0, rr1
  mov \rreg3, \rreg1
  mov \rreg1, \rreg2
  mov \rreg0, #0
 
_loopdiv:
  cmp \rreg2,\rreg3
  blo _divide_end
  sub \rreg2, \rreg3
  add \rreg0, #1
  b _loopdiv
 
_divide_same:
  mov \rreg0, #1
  mov \rreg1, #0
  b 1f
 
_divide_end:
  mul \rreg3, \rreg0, \rreg3
  sub  \rreg1, \rreg1, \rreg3

1:

.endm

syscall.mac :
Code:
sys_exit   =1
sys_write   =4
 stdout   =1
 
.macro syscall iint
 mov r7,#\iint
 swi 0
.endm
[doublepost=1478978297,1478964817][/doublepost]A signed version, using the same macros as above.
$ ./decimal
-2147483648

The more I look at the division code, the more I find it ugly ^^.

Code:
.text
.align 2
.include "syscall.mac"
.include "math.mac"

.global _start

_start:
  mov r5, #1                    @ set the number 1
  ror r5, #1                    @ rotate it to -2147483648
  mov r11, #0                   @ set the digit counter

  mov r2, #1                    @ buffer length, to write one character, or a newline
  mov r1, #0                    @ initialization
  ldr r1, =asciicounter         @ point on =asciicounter
 
  cmp r5, #0                    @ check number's sign
 
  beq _loop                     @ zero, no need to display a sign
  blt _neg
  bgt _pos

_neg:
  rsb r5,#0                     @ two's complement
  mov r6, #45                   @ minus to ascii
  b 1f
 
_pos:
  mov r6, #43                   @ plus to ascii

1:
  str r6, [r1]                  @ store it to be displayed
  syscall sys_write             @ write the sign

_loop:
  add r11, #1                   @ increment the digit counter
  cmp r5, #10                   @ compare the number with 10
  blo _endloop                  @ branch if unsigned < 10
 
  mov r1, #10
  slowdiv r5 r1 r2 r3           @ divide by 10 and get the remainder
  push {r1}                     @ push the remainder on the stack
  b _loop                       @ back to loop

_endloop:
  push {r5}                     @ push the last digit, with the highest weight

_reversed:
  pop {r8}                      @ display the number, beginning with the highest weight
  ldr r1, =asciicounter         @ point on =asciicounter
  mov r6, r8
  add r6, #48                   @ convert the number to ascii
  str r6, [r1]                  @ store it to be displayed
  syscall sys_write
  subs r11, #1                  @ the digit counter is now used for the number display loop
  bne _reversed

  ldr r1, =asciinewline         @ point on the buffer to write, \n, a newline
  syscall sys_write
 
  syscall sys_exit

.data
  asciicounter: .word 0         @ must be initialized
  asciinewline: .ascii "\n"     @ \n is a newline
 
Last edited:
Looks reasonable. A few nitpicks, if you want to see them:
- That register reorg at the beginning of slowdiv looks odd. Can't you just use the registers as they are first of all, then pop the result and the remainder into rreg1 and rreg2 last of all? It only saves you one clock tick I guess, but it would look less odd on first inspection to me at least.

And in your signed div:
- Your negation in _neg could be one instruction quicker, and less of a head scratch while we're at it. You just need to take the number from #0, which you can do with 'rsb r5,#0'.
- Reusing r11 to give you that #0 when you're checking the number's sign is fine, but why not just 'cmp r2,#0'. Doesn't win you any time as far as I can see, unless you can nick something by moving that initial set of r11 somewhere conditional, but at least you could move that r11 initialisation to somewhere closer to where it's being used.
 
Done the changes, thanks !
I'll review the division later, at least it works until it won't run fast enough ^^.
 
Oh, and I also meant to mention that your code to display '10' should never be called, since you have already tested that it's unsigned lower than 10 (and not lower than or the same, which would be the 'ls' conditional code). Looks to me like you've already got rid of that in the signed code though, so perhaps you've already modified your unsigned code.
 
It's still in the unsigned code, but why should I remove it ? The max unsigned have 10 digits.
 
Oh yeah, sorry, think I must have been flicking between the two listings and getting them mixed up. Or perhaps I hadn't noticed that in the loop you're comparing the divided result against 10, then after end: you're comparing the digit counter against 10, and then printing that out before printing out the digits, reversed.
 
Oh I'm also mixed up, I don't print the digits anymore in the signed version.
[doublepost=1479055195,1479027052][/doublepost]I played a bit with the screen.
When quickly switching all pixels from a color to another, after a ctrl-c, the screen can be of both colors.
I suppose that it's linked to the vsync ?
 
How are you switching colours? Is there some palette control you could use, or do you mean you're writing each framebuffer pixel with a new RGB number? If the latter, signals like ctrl-C (SIGINT I think) are handled by OS code which is invoked by some callback interrupt so the OS regularly pops in, even in the middle of one of your loops. The CPU jumps to a predefined address which the OS controls, the OS then checks to see if anyone sent a signal and do other housekeeping, then jump back to the next address inside your loop resetting all of your registers so it looks like nothing happened.

If you're not waiting for vsync, then vsync can't be involved. If you are then you'd be spending comparatively more time waiting for vsync than you were writing the screen, so a ctrl-C would most likely not catch you in the middle of a write. If you're not waiting for vsync you're writing all of the time, and a ctrl-C will always interrupt you in the middle of a write, pretty much.
 
I write every pixel in a loop, then another loop in another color, then restart.
I don't wait vsync.
The color separation is horizontal.
 
Then it's just interripting you when the OS gets invoked to do housekeeping, as it is frequently on any preemptive parallel processing system like Linux. The fact the separation is horizontal just means the display buffer arranges first rows then columns, going first all the way across the screen on the first line, then all the way across on the second line and so on. Older sytstems that are more character focussed generally did more complicated things, but I guess linux buffers are just straightforward.

Edit: I think Linux has some way of flagging that you shouldn't be interrupted just yet, as I've seen that in some programmes. To behave well though, you must allow the system to kill you sooner rather than later, perhaps after a redraw loop. I don't know enough Linux to tell you how to do that though, and I'm not sure you really want to.
 
You mean that the process puts itself in waiting mode ?

I run in init 3 so there's a little cursor refresh from the shell. Dunno how to remove that btw.

Also, when executing I have the stripes of both colors moving up the screen.
Putting loops here and there change their size and speed.
But shouldn't I see rose ?
I guess that writing in the framebuffer is faster than the clock of the screen.

I'm using dmarschal's code btw :
https://pyra-handheld.com/boards/threads/jumping-into-arm-assembly.76469/page-11#post-1363178

--EDIT:
Anyway, here's a code which displays a white-bordered black screen :
Code:
.include "macro.mac"
.globl _start

@ define screen size.
@ 800 x 480, 16BPP.
screensize=480*800*2 

.text
_start:
bl fbinit
@ fbinit will return 0 in R0 if some error occured or
@ the frame buffer memory address if all went fine
mov r11, r0             @ save potential fb adress
cmp r0,#0
bne fbok

print "unable to open/mmap fbdev\n"
exit 255

fbok:                   @ loop to render a white-bordered black screen
  mov r12, #0x2e000     @
  add r12, #0xe00       @ build 800*480/2=192000
  mov r1,#0x000         @ load black

_loopblack:             @ fill the screen with black
  str r1,[r0]
  add r0, #4
  subs r12, #1
  bne _loopblack

  mvn r1,#0             @ load white
  mov r0, r11           @ load fb adress

  mov r12, #400         @ first line length

_looptraithaut:         @ fill the line with white
  str r1,[r0]
  add r0, #4
  subs r12, #1
  bne _looptraithaut

  mov r12, #0x1e0
  sub r12, #2           @ 480 lines - 2 lines

_loopmilieu:            @ fill the rest of the lines until last line
  str r1,[r0]           @ fill the start of the current line with 2 white pixels
  add r0, #1600
  sub r0, #2            @ go at the end of the current line

  str r1,[r0]           @ fill the end of the current line with 2 white pixels
  add r0, #2            @ next line
  subs r12, #1
  bne _loopmilieu

  mov r12, #400         @ last line length

_looptraitbas:          @ fill the last line with white
  str r1,[r0]
  add r0, #4
  subs r12, #1
  bne _looptraitbas

  mov r12, #0x1
  ror r12, #6           @ length of the wait loop

_loopwait:
  subs r12, #1
  bne _loopwait

  mov r0, r11           @ load back the fb adress
  b fbok                @ return in the main loop

@ munmap the memory block and close the file
bl fbclose

exit 0

@ initialize framebuffer device and mmap to a memory address
fbinit:
push {r1-r5,lr}

@ open /dev/fb0 device for read/write
open "/dev/fb0", o_rdwr

@ store frame buffer file descriptor in R4
subs r4,r0,#0

clr r0
@ return 0 if the device was not opened.
bmi fbinitret

@ we use mmap2, defined in sys/mman.h
@void *mmap2(void *addr, size_t length,
@   int prot, int flags, int fd, off_t pgoffset);
@ this is EABI calling convention that uses R4 and R5 as well.
@ OABI needs R4 and R5 pushed on stack.
ldr r1,=screensize
mov r2,#prot_read | prot_write    @ defined in macro.mac
mov r3,#map_shared                @ defined in macro.mac
@ r4=fd
@ r0=0
mov r5,r0
  invoke sys_mmap2

@ load offset of fbp and fbfd
ldr r1,=fbp

@ it gives back 0 if the syscall was not successfull
@ and we use R0=0 for return error code.
cmp r0,#0
beq fbinitret

@ store the frame buffer address to "fbp"
@ and the file handle to "fbfd" in .data section for later use
str r0,[r1]
str r4,[r1,#4]

fbinitret:
pop {r1-r5,pc}

fbclose:
push {r1,r2,lr}

@ load offset of fbp and fbfd
ldr r2,=fbp

@ fisrt, munmap the memory block
ldr r0,[r2]
ldr r1,=screensize
@ arg1=memory block address
@ arg2=length
  invoke sys_munmap

@ then close frambuffer file handle
ldr r0,[r2,#4]
  invoke sys_close

pop {r1,r2,pc}

.data

fbp:  .word 0
fbfd: .word 0

.bss
.end

Its macro.mac :
Code:
macromac   =1

sys_exit   =1
sys_read   =3
sys_write   =4
 stdin       =0
 stdout       =1
 console   =1
sys_open   =5
 o_rd       =0
 o_wr       =1
 o_rdwr       =2
 o_nonblock     =04000
 o_creat   =0100
 s_irusr   =0400
 s_iwusr   =0200
sys_close   =6
sys_getpid      =20
sys_brk       =45
sys_ioctl   =54
 ioc_io       =0
 ioc_read   =0x80000000
 ioc_write   =0x40000000
sys_fnctl   =55
sys_sigaction   =67
sys_gettimeofday=78
sys_mmap   =90
sys_munmap   =91
sys_setitimer   =104
 itimer_real   =0
 itimer_virtual =1
 itimer_prof   =2
 sig_int   =2
 sig_term   =15
 sig_vtalrm   =26
 sig_restorer   =0x04000000
 sig_restart   =0x10000000
sys_sigreturn   =119
sys_clone   =120
sys_nanosleep   =162
sys_mmap2   =192
 prot_read   =1
 prot_write   =2
 map_shared   =1
sys_futex   =240
 futex_wait   =0
 futex_wake   =1
sys_exit_group   =248

@ offsets in timeval struc
tv_sec       =0  @ word
yv_usec       =4  @ word

@ bitmasks in termios flags
isig  =0b0001
icanon=0b0010
iecho =0b1000

tciflush =0
tcoflush =1
tcioflush=2

@ fb_var_screeninfo
fb_xres       =0
fb_yres       =4
fb_xres_virtual =8
fb_yres_virtual =0xc
fb_xoffset   =0x10
fb_yoffset   =0x14
fb_bpp       =0x18

.macro clr rreg
 sub \rreg,\rreg,\rreg
.endm

.macro inc rreg
 add \rreg,\rreg,#1
.endm

.macro dec rreg
 sub \rreg,\rreg,#1
.endm

.macro regloop rreg,oofs
 subs \rreg,\rreg,#1
 bne \oofs
.endm

.macro invoke iint
 mov r7,#\iint
 swi 0
.endm

.macro alloc rreg,bbytes
 sub sp,sp,\bytes
 mov \rreg,sp
lastalloc=\bytes
.endm

.macro free
 add sp,sp,lastalloc
.purge lastalloc
.endm

.macro exit err
 mov r0,#\err
 invoke sys_exit
.endm

.macro print str
 mov r0,#console
 adr r1,1f
 mov r2,#3f-1f
 b 2f
1: .ascii "\str"
3: .align
2: invoke sys_write
.endm

.macro deb sstr
.if debug
 push {r0-r2,r7}
b 1003f
1001:   .ascii "\sstr\n"
1002:   .align
1003:
 mov r2,#(1002b-1001b)
 adr r1,1001b
 mov r0,#console
  invoke sys_write
 pop {r0-r2,r7}
.endif
.endm

.macro open ffname,ffmode
mov r0,pc          @ PC points 2 instructions ahead, to the file name
b 999f              @                                              |
.asciz "\ffname"    @ <<< here ------------------------------------/
.align
999:
mov r1,#\ffmode    @ set file mode attributes
  invoke sys_open   @ and open it
.endm

@ offsets in struc linux_event
 ev_time=0      @ word
 ev_seconds=4   @ word
 ev_type=8      @ short
 ev_code=0xa    @ short
 ev_value=0xc   @ signed word
 ev_size=0x10

ev_press=1
ev_release=0
ev_repeat=2

@ event types
 ev_syn=0
 ev_key=1      @keyboard
 ev_rel=2
 ev_abs=3      @nubs
 ev_msc=4
 ev_sw=5       @lid

@key codes
key_l2=78    @kpplus
key_r2=79    @kpminus
key_power=116    @power
key_hold=152    @coffee

key_1=2
key_2=3
key_3=4
key_4=5
key_5=6
key_6=7
key_7=8
key_8=9
key_9=10
key_0=11
key_backspace=14

key_up=103
key_left=105
key_down=108
key_right=106

key_leftalt=56
key_leftctrl=29
key_menu=139    @menu

key_pgup=104
key_home=102
key_pgdn=109
key_end=107
game_y=key_pgup
game_a=key_home
game_b=key_end
game_x=key_pgdn

key_q=16
key_w=17
key_e=18
key_r=19
key_t=20
key_y=21
key_u=22
key_i=23
key_o=24
key_p=25

key_leftshift=42
key_a=30
key_s=31
key_d=32
key_f=33
key_g=34
key_h=35
key_j=36
key_k=37
key_l=38
key_enter=28

key_comma=51
key_dot=52
key_z=44
key_x=45
key_c=46
key_v=47
key_b=48
key_n=49
key_m=50
key_space=57
key_fn=0x1d0    @fn
 
Last edited:
Yes, that's probably the code running faster than the screen refresh (as it should else writing 3d games would be tricky). If you were to wait for vsync before painting the screen then that should disappear and you'll see frame after frame of a different colour.

Not sure what you mean by 'rose'?
 
I'm no expert on ARM or on assembly, so take these with a grain of salt.
sub r0, #2 @ go at the end of the current line
AFAICT this makes the next store unaligned. Doesn't that create a problem on ARM? I think you want sub r0, #4, or use strh in the next line.

mvn r1,#0xf000 @ load white
What exactly does this put in r1? If it's 0xFFFF0FFF, then that's one white and one cyan pixel. The green bits are the 6 middle ones in a 16-bit pixel, so for magenta, you want 0xF81F, or 0xFC1F for washed out magenta (pink?).
 
Ah, that's where the pink/rose comes from (rose is a perfectly valid colour in english, I guess I was mainly reviewing that code by the comments and hadn't spotted that deviation).

Yes, that mvn will put 0xFFFF0FFF in r1. Is the standard Pandora framebuffer 16-bit? If so, and you actually want 0xFC1FFC1F you can't do that in one instruction, as ARM immediates can only be 8-bits wide (although those 8-bits can be anywhere within a 32-bit number).
 
I'm no expert on ARM or on assembly, so take these with a grain of salt.
AFAICT this makes the next store unaligned. Doesn't that create a problem on ARM? I think you want sub r0, #4, or use strh in the next line.
It's unaligned only for a while between :
Code:
add r0, #1600
  sub r0, #2            @ go at the end of the current line

  str r1,[r0]           @ fill the end of the current line with 2 white pixels
  add r0, #2            @ next line

I don't know if it's safe.

What exactly does this put in r1? If it's 0xFFFF0FFF, then that's one white and one cyan pixel. The green bits are the 6 middle ones in a 16-bit pixel, so for magenta, you want 0xF81F, or 0xFC1F for washed out magenta (pink?).
Exact ! Changed, thanks.
 
I think it's only instructions that need to be aligned, because the program counter is simply missing the bottom two bits that would tell it which byte things started at. I might be wrong, but if that code runs without causing some sort of fatal error, I guess it's okay to str across 4-byte boundaries.
 
Back
Top