don't click here

Conditions in M68K

Discussion in 'Technical Discussion' started by saxman, May 26, 2008.

  1. saxman


    Oldbie Tech Member
    New question

    Code (ASM):
    1.     bset    #6,status(a0)   ; set underwater flag
    2.     bne.s   return_1A18C    ; if already underwater, branch
    I'm confused with this one. Is it that BSET somehow checks to see if the bit it's enabling is already on, and thus if it is already on, the Z flag is set to 1? Because otherwise I'm not entirely sure I understand this.

    Another question

    Code (ASM):
    1.     asr.w   x_vel(a0)
    2.     asr.w   y_vel(a0)   ; memory operands can only be shifted one bit at a time
    3.     asr.w   y_vel(a0)
    4.     beq.s   return_1A18C
    What's being conditioned? Do ASR instructions affect the Z flag somehow? I was looking for a list of instructions that affected the Z flag and didn't find anything on Google. Is there a list out there, or does pretty much any instruction affect it?
  2. notaz


    Tech Member
    Z means "zero flag", it is set (to 1) when something is zero, cleared when it is not. So the op does 2 things, same thing as BTST first, and sets the bit (in status(a0)) after that. Flags are affected only by the BTST part.

    Code (ASM):
    1.     asr.w   x_vel(a0)
    2.     asr.w   y_vel(a0)   ; memory operands can only be shifted one bit at a time
    3.     asr.w   y_vel(a0)
    4.     beq.s   return_1A18C
    What's being conditioned? Do ASR instructions affect the Z flag somehow? I was looking for a list of instructions that affected the Z flag and didn't find anything on Google. Is there a list out there, or does pretty much any instruction affect it?[/quote]
    ASL/ASR affect all flags (including Z), they are quite nasty to emulate. So Z ends up set if y_vel(a0) is 0 after those shifts, and cleared otherwise.

    I would suggest reading the programmer's manual (google for 68kpm.pdf), it will tell you exactly what op affects what flags (well except some undocumented ones :closedeyes: ). This should be faster then asking here.
  3. saxman


    Oldbie Tech Member
    The documentation has been helping a lot. However I did run into something I am not sure about still that maybe someone can explain:

    Code (ASM):
    1. move.w  #0, 2+x_pos(a0)
    What effect does the '2+' have on it?
  4. Tweaker


    Looks like it's taking the value defined by x_pos and adding 2 to it for the destination of the move.
  5. saxman


    Oldbie Tech Member
    So you're saying it's the address of x_pos + 2?
  6. Tweaker


    That's the idea, yeah.
  7. saxman


    Oldbie Tech Member
    Oooh, that's the X position precision value. I'm surprised this wasn't labeled in the source. I had no clue what "subpixel" in the comment beside the code meant when I read it. I thought subpixel had something to do with the art.
  8. saxman


    Oldbie Tech Member
    EDIT #2: Problem solved thanks to Tweaker. Appreciate it!

    EDIT: Okay I figured out my previous question. But I have something very new, more specifically related to Sonic code:

    The stack pointer gets modified, but I see nowhere where it gets changed back, and I see nowhere that anything is written to the stack. So I'm wondering why the stack pointer gets modified. I'm looking at this section:

    Code (ASM):
    1. Sonic_Jump:
    2.     move.b  (Ctrl_1_Press_Logical).w,d0
    3.     andi.b  #$70,d0     ; is A, B or C pressed?
    4.     beq.w   return_1AAE6    ; if not, return
    5.     moveq   #0,d0
    6.     move.b  angle(a0),d0
    7.     addi.b  #$80,d0
    8.     bsr.w   CalcRoomOverHead
    9.     cmpi.w  #6,d1           ; does Sonic have enough room to jump?
    10.     blt.w   return_1AAE6        ; if not, branch
    11.     move.w  #$680,d2
    12.     tst.b   (Super_Sonic_flag).w
    13.     beq.s   +
    14.     move.w  #$800,d2    ; set higher jump speed if super
    15. +
    16.     btst    #6,status(a0)   ; Test if underwater
    17.     beq.s   +
    18.     move.w  #$380,d2    ; set lower jump speed if under
    19. +
    20.     moveq   #0,d0
    21.     move.b  angle(a0),d0
    22.     subi.b  #$40,d0
    23.     jsr (CalcSine).l
    24.     muls.w  d2,d1
    25.     asr.l   #8,d1
    26.     add.w   d1,x_vel(a0)    ; make Sonic jump (in X... this adds nothing on level ground)
    27.     muls.w  d2,d0
    28.     asr.l   #8,d0
    29.     add.w   d0,y_vel(a0)    ; make Sonic jump (in Y)
    30.     bset    #1,status(a0)
    31.     bclr    #5,status(a0)
    34.     addq.l  #4,sp
    37.     move.b  #1,jumping(a0)
    38.     clr.b   stick_to_convex(a0)
    39.     move.w  #$A0,d0
    40.     jsr (PlaySound).l   ; play jumping sound
    41.     move.b  #$13,y_radius(a0)
    42.     move.b  #9,x_radius(a0)
    43.     btst    #2,status(a0)
    44.     bne.s   Sonic_RollJump
    45.     move.b  #$E,y_radius(a0)
    46.     move.b  #7,x_radius(a0)
    47.     move.b  #2,anim(a0) ; use "jumping" animation
    48.     bset    #2,status(a0)
    49.     addq.w  #5,y_pos(a0)
    Just trying to figure out what it's there for. Any ideas? I notice by taking it out, Sonic doesn't go into his jump/roll state as he should. But changing the stack pointer, I mean what does that have to do with putting him into his jump state?
  9. Nemesis


    Tech Member
    You're looking at the definition of pure evil itself. ;)

    In order to understand exactly what's happening here, you have to understand exactly how the stack works. The stack pointer points to a memory location, which represents the "top" of the stack. Every time data is written to the stack, the stack pointer is decremented. IE, if A7=0xFFFF1002, and you write a 2-byte value of 0x1234 to the stack, the stack pointer after the write will be 0xFFFF1000, and the two bytes at 0xFFFF1000-0xFFFF1001 will be 0x1234. When you read from the stack, the stack pointer is incremented. The data itself isn't removed from memory, but the next write to the stack will overwrite whatever is "above" the current address pointed at by the stack pointer.

    There are a number of ways in which the stack can be used. The programmer can manually push data to the stack, either through the use of special opcodes like LINK, PEA, etc, or simply by using it like you would any other address register. The M68000 will also use the stack automatically however. What happens when you call subroutines through an instruction like BSR or JSR is that the current value of the PC is written to the top of the stack. When the M68000 runs an RTS opcode, all it does is read a 4-byte value from the top of the stack, and dump it into the PC.

    There are few requirements in order for the stack to operate correctly. Incorrect use of the stack can cause a multitude of bugs, many of which are extremely difficult and time consuming to track down. Consider the following code:
    Code (ASM):
    1. :Start
    2.     jsr FooBar
    3.     bra Start
    5. :FooBar
    6.     move.l  #$12345678,-(a7)
    7.     rts
    What happens here is that the program starts by jumping to the subroutine FooBar. This subroutine writes the 4 byte value #$12345678 to the stack, then returns. What would happen if you executed this is that it would crash horribly. The subroutine Foobar writes 4 bytes to the stack, but it never removes them. When the rts opcode executes it reads the top 4 bytes off the stack, and writes it to the PC. At this point however, the top 4 bytes are 0x12345678. The rts opcode would remove the 4 byte value on the top of the stack, and instead of the PC returning to the location the subroutine was called from, the PC would now be 0x12345678.

    This is one example of what can go wrong with the stack. Another potential problem is a "stack overflow", where more and more data is written to the stack which never gets removed, and eventually, the stack overwrites all the memory and trails off into the great beyond. In assembly, poor stack management can be a common source of bugs. In order to minimize the occurance of these problems, it's important to employ some "good practice" guidelines for safe the use of the stack. Here are the 3 cardinal rules for safe use of the stack, IMO:
    1. Every value which is manually written to the stack by the programmer should removed from the stack in the same "block" of code. Don't push data to the stack in one place, jump to a completely different segment of code, and remove it from the stack there. Everything should be removed at the same level of scope.
    2. Remove data from the stack in a consistent manner to the way it was written. Eg, if you write 2 2-byte values to the stack, don't remove them using a 4-byte operation, use 2 2-byte operations. This is very important, as writes to the stack are often "aligned".
    3. Don't directly manipulate the values which the CPU writes to the stack, like return addresses from subroutine calls.

    Now, back to this code. What's happening here is that the programmer has added 4 to the value of the stack pointer. What does that do exactly? Well, since the stack pointer points to the last value written to the stack, what they've effectively done is "removed" a 4 byte value from the top of the stack. The implications of that depend on what that value was. It's a little hard to tell without seeing the code in context, but it seems that they've broken rule number 1. You can't see any place where they've written to the stack in that code segment, so that means some other peice of code has pushed the last value to the stack. You can't tell from looking at this code where that data came from, what it was, or why it's being removed, which is one of the reasons you shouldn't do that.

    In this case, it's possible that the last value written to the stack is actually the return address for the call to this subroutine. This would mean they've broken rule number 3, by directly manipulating the return address which has been saved by the M68000. The effect of this would be that when the RTS opcode executes, the PC doesn't actually return to the location it was called from. It would pull the next value from the stack and return to that location instead. It's not uncommon to see this trick used in old assembly to do a "multi-level return". Nowadays, it's a capital offence.

    The easiest way to see what's really going on with the stack is to step through code which uses it, and keep an eye on the stack register and the memory it points to as the code runs. You can do this with the debugger in Gens.
  10. Upthorn


    TAS Tech Member
    In simpler terms than nemesis used, every jsr and bsr operation stores on the stack the four byte address of the next instruction after the jsr.
    for instance
    Code (Text):
    1. ROM:00003134                 jsr     BuildSprites
    2. ROM:0000313A                 moveq   #0,d0
    The jsr at 00003134 stores "0000313A" on the stack, and decrements sp by 4. The rts at the end of BuildSprites then reads "0000313A" from the stack, jumps to it, and increments sp by 4. Now, if BuildSprites added 4 to the sp before returning, it would effectively double the rts, so that in addition to returning from BuildSprites, the rts would return from whatever called BuildSprites in the first place. Adding 8 would effectively triple the next rts, and so on.
    Now, this only works because sonic games rarely ever store data on the stack, and will crash spectacularly if you ever accidentally end up incrementing the stack pointer past the first level of consecutive JSR/BSR return address storage.