This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Assignment macro

Assignment macro
by on (#100676)
I was reading rainwarrior's thread on using start for randomness. ( viewtopic.php?f=2&t=9364 )

I noticed this:

.macro stb           val, addr
; - store immediate byte using A as temporary register
    LDA #val
    STA addr
; ....
 stb $0f, $2007
 stb $12, $2007

I understand this was from a quick example, but seems I like writing macros, maybe see what you think of this syntax:

wb, for write byte

wb $2007 := #$0f

PPU_DATA = $2007
wb PPU_DATA := #$0f
lda #$0f
ldx #$02
wb PPU_DATA := a
wb PPU_DATA := x
wb PPU_DATA := a

There is also a decent ww (write word) macro. ... macro.html
Re: Assignment macro
by on (#100679)
Ah, my goal there was not so much to change the syntax as it was to do two lines of work in one line, for readability. I don't think I'd want to switch to pascal style assignment operators, since it doesn't appear to save any space.

Actually, something I might appreciate more is a compact way to express storing a string of bytes to a memory mapped register:

repstb $2007, $01, $02, $03

; equivalent to:

lda #$01
sta $2007
lda #$02
sta $2007
lda #$03
sta $2007

I haven't looked into whether ca65 supports variadic macros though. I presume it does.

This could be useful for pregenerated display lists, or CHR changes that need to occur quickly during vblank. It wouldn't be as optimal as using all 3 registers and skipping unnecessary loads, but it would be suitable for quick testing at least. In my example I could have written my palette assignment in one or a few neat lines, instead of one line per byte.
Re: Assignment macro
by on (#100685)
I realize that you were just trying to make the code a bit nicer to look at. That's my goal too, I prefer an assignment operator to a comma. It's trivial to change the assignment to just an equals sign, just change it to such in the macro. I used to code in pascal so I went with that operator. Save space in the source or code? The result of the macro is similar to yours.

You could do what you posted easily, with the benefit of checking if the next byte is the same as the last and skipping the next lda #XX instruction if so. (Which is the motivation in my macro for allowing you to assign the value in a register.)


.macro repstbs port, b1, b2, b3, b4, b5, b6, b7, b8, b9, lastbyte
   .if (.paramcount > 10) .and  (.not .blank({b9}))
     .error "Too many parameters"
   .if .not .xmatch({lastbyte}, {b1})
     lda b1
   sta port
   .ifnblank b2
     repstbs port, b2, b3, b4, b5, b6, b7, b8, b9 ,,b1

If you want a variable number of parameters, I think you are going to have to define something else for a delimiter, or enclose your list of values in { } and use pseudo functions to evaluate them.
Re: Assignment macro
by on (#100692)
ca65 macros can be recursive? that's pretty neat.

that's weird that it has .paramcount but not .param, which I could turn into something like:

.macro rebstb
    .repeat .paramcount(-1), I
        lda #.param(I+1)
        sta .param(0)
Re: Assignment macro
by on (#103744)
So I expanded on this idea quite a bit, I'll link code soonish. It's pretty useful imo at this point and I think most bugs are fixed.

I decided to name the macro 'mb' so it's easy to type primarily. It stands for Move Byte, since that is mostly what is does.
I guess you could also define an underscore to do the same thing perhaps for readability to make it look like more natural code in the source.

So what it does is figure out what is on the right side of an assignment and assign it to whatever is on the left. It's pretty flexible at figuring out what to do, and should work as expected:

mb foo := bar

So, foo and bar can both be either a 6502 register or memory address (variable). If for example you did:

mb x := a

It will output a tax instruction. The left side is limited to a memory address or register, but the right side can also be a simple expression:

mb x := CurrentWorld + #1 & #%00000111

It will figure out the right side is going to have to use the accumulator, output the correct code, then do a tax. If the assignment was the accumulator, there would be no output for the assignment (since the accumulator is already holding the result).

If you have two variable names and no indication of the register to use, the default is to use the accumulator. This can be overridden as:

mb x, var1 := var2

This will output: ldx var2 \ stx var1

This only forces the register on the right side - if the left side is a different register assignment still happens with the register indicated.

Forcing is only useful where it is not obvious what must be used, though you could force it all the time if you wanted to be explicit - you will get an error if you try to force an index register with functionality that requires the accumulator. Code that specifies a register 1st after the := will behave the same as a forced register. ( y := x + 3 will force using reg x on the right side... see below)

Support for the following:


&     bit and
|     bit or
^     bit eor
+     add, clear carry 1st
++    add with carry
-     sub, set carry 1st
--    sbc with carry
<<    shift reg a left
>>    shift reg a right
*     multiply
/     divide

Shifting requires a plain constant value after the operator and will output that many asl or lsr commands. Multiply or divide also require a constant value of power of 2 (same as shifting of course.)

You can also use a single - or + with x and y followed by a plain constant value - that is how many decrement or increment commands will be output.

This is intended for simple, very close to assembly type code, so there is no support for brackets in the expressions or trying to use more than one register at a time in a single expression, though brackets will be skipped if you want to use the operators to write a constant expression.

I have also integrated this into the logical block code, as in this part of the timer code from smb:


    ; if game timer not yet at digits at 000
    if GameTimerDisplay | GameTimerDisplay[1] | GameTimerDisplay[2] == not zero

        ; if timer = 100
        if ( y := GameTimerDisplay - 1 == zero )  && ( GameTimerDisplay[1] | GameTimerDisplay[2] == zero )
            mb EventMusicQueue := #TimeRunningOutMusic

        mb GameTimerCtrlTimer := #$18       ; reset game timer control

        mb y := #$23                      ; set offset for last digit
        mb DigitModifier[ 5 ] := #$ff   ; set value to decrement game timer digit
        jsr DigitsMathRoutine            ; do sub to decrement game timer slowly
        mb a := #$a4                      ; set status nybbles to update game timer display
        jmp PrintStatusBarNumbers     ; do sub to update the display


The expression in the if condition share the same code as 'mb'.
As well, the [ constant ] syntax can be used rather that + constant. You can also include x or y:

mb VRAM_Buffer1[ x - 1 ] := WSelectBufferTemplate[ x ]

For indexed (I find it more readable.)

Any questions, feedback or criticisms welcome.

BTW I agree that would be nice to have .param as rainwarrior described. You can mostly get that functionality with .mid and recursive calls, but not as nicely.

EDIT: This macro is included as a part of ca65hl.