Sunday, October 28, 2012

HL macros technical

A more technical explanation of the HL macros I created, so others will feel more comfortable using them, as well as for myself in the event I forget how they work.

I'll be focusing mainly on the if macro due to the fact it was first, and the other macros are very similar.

To begin, somewhere in code the ca65 runs into the macro 'if'. From beginning to end, this is what the macro logic does:

1. Pass the entire parameter token list to the macro. There is only one parameter, commas will cause problems unless the parameter is enclosed in {}. (Example: Passing an indexed mnemonic)

2. Check for goto label on the end of the parameter. If there is a 'goto', strip it and the label from the parameter string and call the main if_macro with the label as the last macro parameter.
(The syntax goto can be changed to user preferred syntax - look for the line:
 ".define _USER_GOTO_DEFINITION goto")

3. In this macro, check if the first character is a bracket. If so, find the matching closing bracket. If the closing bracket is on the outside, strip the outside brackets.

4. Scan from left to right for the 1st matching OR symbol (user defined, default: || ). If found, split the parameters into everything to the left of the OR and everything right of the OR. Everything to the left is recursively expanded with recursive and leftsideOR set, everything to the right is recursively expanded with recursive (or possibly lastrecursive if no more OR/AND found) set. Setting leftsideOR signals that expansion that there are more branch(es) to follow and the logic to combine them is OR.

Setting recursive signals to the macro expansion that it should not end the branches (there is more to come)  and that it should not increment the current if block counter. If a goto label was set it is always passed in the recursive expansions.

5. If no more OR, test for AND. This works similarly to the OR, but sets leftsideAND, and results in logic that combines the following branch instruction with AND logic.

The macro code has no ability to properly handle more than one logical test to the left of an AND or OR due to how this works. It may be possible to make it work, but it would result in a much more complex macro and not nearly as efficient 6502 code in my opinion. As well OR logic must be first to make things easier for the macro and the 6502.

6. Scan for any more ANDs or ORs on the right of the last AND/OR. If none found, the last recursive call sets lastrecursive, which is only to signal to the macro to not increment the if counter. Everything else happens as if it was a regular simple if statement.

During any of this, regular brackets are honored and will only be scanned inside as described in step 3. This allows for placing an OR after an AND, ie if cond1 && (cond2 || cond3) will work properly, as well as recognized symbols to be passed to custom macro 'functions'.

After this step, it would be similar to coding all these if macros, line by line with the different parameters set to combine the logic. The tokens in between the OR and AND (the conditions to 'evaluate') symbols are passed to another macro expansion that checks for a valid flag to be tested and turned into a branch instruction. If it does not find a flag in the form of foo bar, where foo is the flag (C, Z, N, V, G) and bar is the default (set, clear) it will assume that it should be including the code from a macro, or some inline instructions:

It will attempt to create code from anything up to a + symbol. If this is an instruction or macro, it will be output, scan for another instruction or macro and output it until an equality ( == ) is found, or, if no equality symbol, a compatible macro sets the flags with set_flag_test 'command'.

Code explanation

I've been testing this a lot with Super Mario Brothers, and rewriting some of the disassembly for testing and to hopefully make it more readable, which results in some good examples:


;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:

;HL if macro:

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

This does produce the same code, so a closer look of how:

The first test is a macro that is called and expanded resulting in the assembly:

lda OperMode
cmp #VictoryModeValue
set_flag_test Z set

After that the Z set test is interpreted to the correct branch instruction due to the leftsideOR setting in the recursive macro call. A branch to the beginning of the code to be executed is created.

beq IF_CODE_BLOCK_START_LABEL_0001

So if this test is true, no other branches would need to be checked.
The next comparison macro expanded:


cmp #GameModeValue
set_flag_test Z set
This time, however, leftsideAND is set and because this test is now required to be true to continue, the flag is negated and a bne instruction to endif  is generated.


bne _END_IF_0001

If the test passed then there is one more, and this (because it is last) is treated like a regular if without any recursive restrictions except that the if counter is not incremented. (This also results in the creation of the  IF_CODE_BLOCK_START_LABEL_ ) This test is also required to pass to enter the code block, so the opposite condition is branched on:

; the macro is expanded:
lda OperMode_Task
cmp #$03
set_flag_test Z set

; the branch is negated:
bne _END_IF_0001


The complete code output would look like this:
  lda OperMode
  cmp #VictoryModeValue
  beq IF_CODE_BLOCK_START_LABEL_0001
  cmp #GameModeValue
  bne _END_IF_0001
  lda OperMode_Task     
  cmp #$03
  bne _END_IF_0001
  IF_CODE_BLOCK_START_LABEL_0001:

   ;code block

_END_IF_0001:
Of course, you never see this code, but this is what the macro is creating. I feel some low-level coders are afraid that higher levels constructs take away their control and perhaps produce poor code, so I also hope to show that this is not a true concern if you understand how the macro code is working, and how to avoid those pitfalls and make coding easier. The other looping macros are very similar.

If goto is used, the macro will behave very differently and not use any counters, and invert the test. Normally the test will jump to the label (_ENDIF_IF_) if it fails, but with goto the code will jump to the user defined label if it passes.

The macro will also create efficient long jumps (jmp) if set_long_branch + is set.

 
set_long_branch +

; If start not pressed, and start and A not pressed then:  
  if ( comp a <> #Start_Button )  && ( comp  a <> #( A_Button+Start_Button))  
 
  set_long_branch -

This results in:
  cmp #Start_Button
  beq _IF_ENDIF_JMP_
  cmp #A_Button+Start_Button
  bne IF_CODE_BLOCK_START_LABEL_0001

_IF_ENDIF_JMP_:    
  jmp _END_IF_0001             
IF_CODE_BLOCK_START_LABEL_0001:
Which is the same as Super Mario Brothers code:

  cmp #Start_Button
  beq StartGame
  cmp #A_Button+Start_Button  ;check to see if A + start pressed
  bne ChkSelect               ;if not, check select
StartGame:    
  jmp ChkContinue             ;if start or A + start, execute
ChkSelect:    
Update: As well I'd like to demonstrate what is possible with the inline code as well: 
Using the previous example:
if (comp OperMode = #VictoryModeValue) || ((comp a = #GameModeValue)  && (comp OperMode_Task = #$03))     
This can also be written as:
if (lda OperMode + cmp #VictoryModeValue == equal) || ((cmp #GameModeValue == equal)  && (lda OperMode_Task + cmp #$03 ==  equal))     
If you don't feel like writing a macro or you want more control over what is happening in a specific case.

For now that's it, I don't know if I am going to write up too many more macros like this, possibly a switch statement if I find I need it, or possibly an elseif to use with the if-endif.

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.

Sunday, October 21, 2012

ca65 Tokens revisited.

Quick note on ca65 tokens. The source file with the enumeration I posted here is pretty helpful, but I noticed that ca65 will actually recognize C style syntax boolean and/or ( && and || ) as a single token, which isn't obvious at all from that source listing. I checked the source and in scanner.c you can see what string data is actually matched to what. Note: &, &&, |, || are different tokens, and && is actually the exact same thing as .and, likewise || is the same as .or (they match the exact same internal token value). As well I found it interesting that '==' is not a token - it will be scanned as two equal tokens, so macro code must be exended to match only '=='.

Friday, October 12, 2012

CA65 Highlevel Macro Updated

I've added a cleaner method for adding functions to the IF/WHILE statements for better readability.
The idea is for a user of the macros to write a simple macro that results in some flag being set (in the CPU) and then set that flag so the conditional check knows what code to generate. You can achieve some nice things with it, and as an example and for my own use I've written both a signed and unsigned comparison function.

First, how to make a function - a simple example:
     

.macro reg_is_negative reg
  .if .xmatch(reg,a)
    pha
    pla
 .elseif .xmatch(reg,x)
    txa
 .elseif .xmatch(reg,y)
    tya
 .else
   .error "no register"
 .endif
  set_flag_test N set
.endmacro
You can do anything you want like a normal macro, just add set_flag_test followed by the flag you want to test. You can still use all the .defines as well form the first post on this: http://mynesdev.blogspot.ca/2012/09/ca65-highlevel-macros.html So in this example you could:
if reg_is_negative a
;code
endif
The if macro "knows" to check for the status of the N flag, where N set means TRUE.

Macros comp and scomp

 Both of these macros accept the same values and can evaluate anything on either side of one comparison operator except for a comparison of two registers (they can only evaluate one comparison, no and or or). If using a greater or less compare with a low byte or high byte operator, you must enclose the operator in brackets: Use would be like this:
if scomp a >= #(<-29)
; code
endif
There is not much more to it, operators supported are the standard <, >, <=, >=, =, <>. You can also use not at the beginning as so:
if not scomp a >= #(<-29)
; code
endif
Another example..
if comp my_var1 >= #29
; code
endif
Full example code here: https://app.dumptruck.goldenfrog.com/p/b9UI5-8_ck

Monday, October 1, 2012

Assignment macro!

Quick macro for CA65 easy to read assignment, using variable labels or actual values. wb or ww for writebyte or writeword:

; assign immediate to a variable:

wb my_var := #23

; copy variable value to another:

wb my_var := my_other_var

; write to memory:

wb $2007 := #20

; write word:

ww score := #$1234

; copy word sized variable:

ww highscore := score

; assign value of register example ( a x or y)

wb p1score := #0
wb p2score := a
wb highscore := a


Code here: (updated..x1)


.macro findequal exp
.if .xmatch(.mid(_equalpos_,1,exp),:=)
.exitmacro
.else
_equalpos_ .set _equalpos_ + 1
.if (_equalpos_ >= .tcount(exp))
.exitmacro
.endif
findequal exp
.endif
.endmacro

.macro wb exp
_equalpos_ .set 1 ; first check at token 1
findequal exp
.if (_equalpos_ >= .tcount(exp))
.error "No assignment in poke macro"
.exitmacro
.endif

.if .xmatch(.mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp}),x) ; assign reg x
stx .mid(0,_equalpos_,{exp})
.elseif .xmatch(.mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp}),y) ; assign reg y
sty .mid(0,_equalpos_,{exp})
.elseif .xmatch(.mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp}),a) ; assign reg a
sta .mid(0,_equalpos_,{exp})
.else
lda  .mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp})
sta .mid(0,_equalpos_,{exp})
.endif


.endmacro

.macro ww exp
_equalpos_ .set 1 ; first check at token 1
findequal exp
.if (_equalpos_ >= .tcount(exp))
.error "No assignment in poke macro"
.exitmacro
.endif

.if  .xmatch(.mid((_equalpos_+1),1, {exp}), #) ; immeidate mode

lda  #<(.mid((_equalpos_+2), .tcount({exp}) - _equalpos_  - 2 , {exp}))
sta .mid(0,_equalpos_,{exp})
.if (>(.mid((_equalpos_+2), .tcount({exp}) - _equalpos_  - 2 , {exp}))) <> (<(.mid((_equalpos_+2), .tcount({exp}) - _equalpos_  - 2 , {exp}))) ; high byte not equal to low byte
lda  #>(.mid((_equalpos_+2), .tcount({exp}) - _equalpos_  - 2 , {exp}))
.endif
sta .mid(0,_equalpos_,{exp}) + 1

.else

lda  .mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp})
sta .mid(0,_equalpos_,{exp})
lda  .mid((_equalpos_+1), .tcount({exp}) - _equalpos_  - 1 , {exp})+1
sta .mid(0,_equalpos_,{exp}) + 1

.endif


.endmacro