Monday, October 22, 2012

ca65 HL Macros updated again

Another, but big update. Just posting some quick info for now, perhaps better documentation to come.

Quick summary of features:

Basic flag test syntax:

if (flag condition) 
   ;code
endif

Flag refers to 6502 CPU status flags, Z, C, N , V as well as an added flag I called G for greater or less or equal tests. the text inside the brackets above can be any of the flags plus 'set' or 'clear' (required). This is the simplest form of the macro. Example: C set would result in true if the carry flag was set after an instruction:

ror a
if C set
; do code
endif
You can add readability by creating or using the .define macros in the source file, so you could use 'zero' for example, and ca65 will substitute Z set.

Macro 'Function' Calling

The next feature I added was the ability for the expression evaluation to try and call a macro if it didn't recognize the flag to be tested. You can name any macro as a condition to be tested as long as the macro ends with 'set_flag_test foo', where foo is the flag to test:

.macro padpressed buttons
   lda _pads_new_pressed
   and #(buttons)
   set_flag_test Z clear
.endmacro

;elsewhere:

if padpressed BUTTON_A
; do stuff
endif

AND and OR Evaluation

Now there is also and and or support. There are some rules due to the way the macro code and 6502 work: - All ORs must be before ANDs in the same bracket level - There cannot be more than one test on the left side of an AND or OR in a bracket. For example:  

If (zero and carry) or minus 

The test on the left of the OR will not assemble. - You can place multiple test in brackets on the right of an AND or OR:     

If minus or (zero and carry) 

This is okay.

The negate symbol (! or not by default) currently will only work for one test, not a set of brackets. All tests are short circuited for the best possible code. An example from Super Mario Brothers:

if (comp OperMode = #VictoryModeValue) || ((comp a = #GameModeValue)  && (comp OperMode_Task = #$03))                                                               
; code block
endif

;original

  lda OperMode           ;are we in victory mode?
  cmp #VictoryModeValue  ;if so, go ahead
  beq ChkPauseTimer
  cmp #GameModeValue     ;are we in game mode?
  bne ExitPause          ;if not, leave
  lda OperMode_Task      ;are we running game engine?
  cmp #$03
  bne ExitPause          ;if not, leave
  ChkPauseTimer:
   ;code block
ExitPause:

The code in the if structure assembles to the exact same machine code as the block below it. Just for the sake of understanding what is required of the syntax, please note that this would work as well:

if comp OperMode = #VictoryModeValue || comp a = #GameModeValue  && comp OperMode_Task = #$03                                                               

The brackets do not matter in this case, but they can be useful in the case of having an and before an or:


if comp a = #GameModeValue  && (comp OperMode_Task = #$03 || comp OperMode_Task = #$04)
As well , if you needed to pass an and or or symbol to a macro you could do so with brackets. Brackets are only processed by the macro code if necessary. It will look inside a bracket pair if that is all there is (outside brackets over the entire expression) or if brackets are the next thing after an and or or. But if the brackets are part of an argument to a macro they will be left alone. Example:

 if scomp #(<-5) < xcoord
Long Jumps

There will come a time when a long branch is needed. This can be turned on with set_long_branch:

set_long_branch + ; turn on long branching
set_long_branch - ; turn off long branching
set_long_branch +,- ; turn on long branching turn off warning messages
All branching code will use a jmp opcode when long branching is turned on. If the macro is able to branch without a long jump (sometimes not 100% accurate at this point) it will output an error message unless messages are turned off as shown above.

Other Features

Besides the standard IF-ENDIF shown above, there are a number of other supported syntaxes and features:

if-else-endif

Use a else with the if-endif block.

if - goto label

Use an if with a conditional expression ending with a goto label to jump to that label, where label is a valid label in your code, rather than creating an if-endif block.

do-while

A do while code block. Code between the do and the while will be executed until the expression after the while is false.

repeat - until

This is the same as the do-while block but the conditions are reversed - repeat until the expression is true.
Note: The keywords do and repeat are exactly the same.

while-do-endwhile

The while keyword starts this code block. To differentiate between this and do-while, the while must end in do.. example:

  while not buttonpressed BUTTON_A do
    jsr read_pad
  endwhile

Inline code:

Sometimes it may be clearer to read code if it is included as part of the conditional code. For example:

  do
    lda Timers,x
    if not zero
      dec Timers,x
    endif
    dex
  until negative
This isn't too hard too read, but consider:

  do
    lda Timers,x
    if not zero
      dec Timers,x
    endif
  until dex == negative
This, to me, is even more readable. This also allows for some flexible coding solutions:

; orignal example:

  lda TimerControl
  beq DecTimers    
  dec TimerControl
  bne NoDecTimers
  DecTimers:
  ;code block
  NoDecTimers:

  
This is pretty difficult to change into an if structure, but:

  if (lda TimerControl == zero ) || (dec TimerControl  == zero)
  ;code block
  endif
..does the exact same thing. The use of the == indicates the end of code and the start of what flag is going to be used to determine the branch. Assembly commands can be separated by + and you can mix in macros as well, or end the list of commands with a macro if it sets set_flag_test.

 For now, a block of example code from SMB:

  lda GamePauseStatus
  lsr
  if carry clear
    if (lda TimerControl == zero ) || (dec TimerControl  == zero)
      ldx #$14
      dec IntervalTimerControl

      if negative
        lda #$14
        sta IntervalTimerControl
        ldx #$23
      endif

      do
        lda Timers,x
        if not zero
          dec Timers,x
        endif
      until dex == negative

    endif
    inc FrameCounter
  endif

continued...

ca65 reads commas as macro parameter seperators. An example of where curly braces are needed:


  do
    if {lda Timers,x == not zero}
      dec Timers,x
    endif
  until dex == negative

;original code:

DecTimersLoop: 
  lda Timers,x              ;check current timer
  beq SkipExpTimer          ;if current timer expired, branch
  dec Timers,x              ;otherwise decrement the current timer
SkipExpTimer:  
  dex                       ;move onto next timer
  bpl DecTimersLoop  



Code here: http://pastebin.com/azMMvh4r updated 1 time since this post was first created.

No comments:

Post a Comment