Search code examples
assembly

How to branch multiple times in SuperH assembly?


I wrote a SuperH assembly subroutine that draws a pixel to the screen:

# r3: x coord
# r4: y coord
# r5: pixel color
draw_pixel:
  mov.l frame_buffer_address, r0
  mov.l screen_width, r6
  mul.l r6, r4
  sts macl, r4
  add r4, r3
  add r3, r0

  mov r5, r1
  mov.b r1, @r0 

  rts 
  nop 

I can then branch from my program with:

  mov #20, r3
  mov #50, r4
  mov #10, r5
  bsr draw_pixel
  nop 

This works perfectly, and I can do it multiple times with different parameters, it all works.

I start to run into trouble when I try to branch to draw_pixel from another subroutine. (I want to draw a rectangle but for now I am just drawing a single pixel until it works correctly)

draw_rect:
  mov #50, r3
  mov #20, r4
  mov #4, r5
  bsr draw_pixel
  nop

  rts
  nop

Which I try to call from my program with:

  bsr draw_rect
  nop

This does not work. I suspect it has something to do with branching to draw_rect to draw_pixel, since the code is identical as the code that worked, the only thing that has changed is just an additional subroutine / branch in the mix.

I feel like there is something extra I need to consider when doing nested branching, but not exactly sure what that would be. Or perhaps there is a different sort of mistake I'm making here.

Also please note I'm using immediate values just for testing, I'll use variables once I get it working.

Edit:

The solution is to save pr before branching to draw_pixel:

sts pr, r9

and then restore pr before returning from draw_rect:

lds r9, pr

Solution

  • According to the manual the bsr instruction performs the following operations:

    Delayed branch, PC + 4 → PR, disp × 2 + PC + 4 → PC

    In other words, it first saves the return address in the procedure register (PR), then sets the program counter (PC) to the address of the procedure.

    Equivalently, the rts instruction does:

    Delayed branch, PR → PC

    That is, it restores the program counter value that was previously saved by bsr.

    Here's the problem: the first time you use the bsr instruction (bsr draw_rect), the processor saves the address of the next instruction in PR, so it knows how to return from the procedure. Then you use bsr again (bsr draw_pixel) and it does that again. So you're effectively losing the original value of PR.

    When draw_pixel returns, it will return to the nop after bsr draw_pixel, as expected. However, when draw_rect returns, it will also return to the same place, since that's where PR points to.

    To solve that, you need to save PR somewhere else (another register or the stack), then restore it before returning.