Some ASM tutorials in regards to making NES Maker stuff

darkhog

New member
Things like how to set up your custom variables in RAM (both number and e.g. strings) how to do loops and ifs in ASM.

More oriented to high-level programmers familiar with languages like C# or GML and not with ASM.
 

DChilders84

New member
ASM is a procedural language. You're not going to see ifs or loops in ASM, at least not like you'd be familiar with from a high-level language.
 

Bucket Mouse

Active member
DChilders84 said:
ASM is a procedural language. You're not going to see ifs or loops in ASM, at least not like you'd be familiar with from a high-level language.

ASM has subroutines...it has a command to repeat something up to a specific number before moving on....those don't count as loops?
 

darkhog

New member
DChilders84 said:
ASM is a procedural language. You're not going to see ifs or loops in ASM, at least not like you'd be familiar with from a high-level language.

That's why I'm asking for a tutorial that would show 6502 ASM equivalents of common high-level instructions (preferably using ASM6 syntax since that's what NES Maker's engine uses), Sherlock.
 

Kasumi

New member
No need to be rude. Anyway, after most instructions some bits in a byte are set or cleared. You can "branch" to a new location in the code based on the state of these bits. Branch is a conditional jump. If the condition is true, the CPU begins running at the specified label, otherwise it continues to the instruction immediately after the branch.

Code:
if(variable == 2){
//stuff to do when variable == 2
}
Code:
lda variable;Copy the value of variable into a convenient place to check it (the accumulator)
cmp #2;CoMPare the value in the accumulator to the number 2.
bne variableisnottwo;Branch if Not Equal to skip the code that should only be run when the variable is equal to two
;Stuff to do when variable == 2
variableisnottwo:
If statements are kind of reversed. You write "skip the code when the condition is not true" rather than "run the code when the condition is true". Same result obviously, but the distinction helps one make them.

Code:
for(int x = 0; x < 10; x++){
//Stuff to do 10 times

}
Code:
ldx #0;Copy the number 0 into X. One can easily subtract one from or add one to X, as well as access data X bytes away from a given address.
loop:
//stuff to do 10 times
inx;Add one to X. INcrement X.
cpx #10;You want to do 0-9, but not 10 so when the add makes X equal to 10, we don't want to loop,
;CPX is like CMP in the if statement example. Except it CoMpares X to the given value instead of comparing the accumulator to the given value.
bne loop;If X has not yet reached 10, we want to run the loop again

Bonus: If you want to do something 10 times, but you don't need to use X itself to access the data, this is faster.
Code:
ldx #10;The number of times you want to loop
loop:
//stuff to do 10 times
dex;Subtract one from X. DEcrement X.
bne loop
It's faster because it doesn't run the cpx instruction at every iteration of the loop.
It works because the flags are updated after dex, and there's an explicit flag and check for 0.
Basically you often want to write your for loops backwards from how you might usually in something higher level provided you don't need to access X bytes from somewhere for data access or something. (But even then, it's beneficial to just store the data backwards too)

Code:
for(int x = 10; x >0; x--){

}
instead of
Code:
for(int x = 0; x < 10; x++){

}
All the for loop examples are roughly equivalent to the higher level code (assuming the loops don't modify X in any other way), but the structure of them is not because the assembly language versions will always run the loop at least once. (Edit: Secondary disclaimer: They really check for equality rather than less than or greater than. But greater than or less than checks actually involve a more complicated flag, and usually this is equivalent behavior to what one wants when this type of for loop is written anyway.)
For example:
Code:
for(int x = 0; x < 0; x++){
//This code would never run because zero is not less than zero
}
Code:
ldx #0
loop:
//Stuff to do zero times. Except. It'd run WAY more than zero times
inx;X was zero in the first iteration. Now it's 1, so we actually loop again. Then again for 2, and 3 and 255 until we get back to zero.
cpx #0;Technically not needed because inx automatically checks for zero. But the code is still broken.
bne loop
Even if you "fix" the broken parts of the above, it's not really equivalent. There's not really a general structure for a for loop. If your exit condition is <= rather than < what you need changes, if you can't do an explicit zero check what you need changes, if you want to do something other than add or subtract one to track the exit condition, what you need changes.
Code:
while(variable == 20){
//code here
}
Code:
loop:
lda variable;
cmp #20;If variable is not equal to 20
bne endloop;don't run the loop

;code here

jmp loop;Jump is a non conditional branch. We return to the conditional check.
endloop:
Another way to do it is this:
Code:
lda variable;
cmp #20;If variable is not equal to 20
bne endloop;don't run the loop
loop:
;Code here
lda variable;
cmp #20;If variable IS equal to 20
beq loop;continue to loop. BNE was branch if not equal. BEQ is Branch if EQual.
endloop:
Which looks a little weird, but is faster. Unfortunately you need the early exit.

Infinite loop is super easy:
Code:
while(true){
;code here
}
Code:
loop:
;code here


jmp loop
To "break" the infinite loop, you just jump (or branch) to a label immediately after it. (But you could jump or branch anywhere else too.)

Anyway, if you already know a higher level language I recommend learning 6502 from https://skilldrick.github.io/easy6502/ because as the for loop examples covered there's a lot of nuance to some of this. It's hard to give a quick rundown of just loops without teaching the language because there are a LOT of ways to put a loop together, and most are good at least some of the time.

Ask specific questions about the things you don't understand in easy6502 if you need to. You can't really shortcut around actually learning the language once you want to do something complicated.
 

Mihoshi20

Member
The ASM6 documentation, which for some reason isn't distributed with NESmaker in the GameEngineData where ASM6 is stored does state that it supports at least some rudimentary form of IF/ELSEIF/ELSE/ENDIF functionality though I've never tried to use it. I tend to stick with native assembly loops. It is there though for those who want to experiment with it.

Code:
IF / ELSEIF / ELSE / ENDIF

        Process a block of code if an expression is true (nonzero).

                IF j>0
                    DB i/j
                ELSE
                    DB 0
                ENDIF
 

Kasumi

New member
That's for conditionally assembling things, Mihoshi20. If the condition is true, the code ends up in your program.If the condition is false,the code does not end in your program.

GGSound uses them to disable features. (This saves space and RAM over having bytes in the program for features the user isn't using.)
Code:
;Get volume envelopes address.
    ldy #0
    lda (sound_param_word_2),y
    sta base_address_volume_envelopes
    iny
    lda (sound_param_word_2),y
    sta base_address_volume_envelopes+1

    ifdef FEATURE_ARPEGGIOS
    ;Get arpeggio envelopes address.
    iny
    lda (sound_param_word_2),y
    sta base_address_arpeggio_envelopes
    iny
    lda (sound_param_word_2),y
    sta base_address_arpeggio_envelopes+1
    endif
If FEATURE_ARPEGGIOS is defined, all the code under "Get arpeggio envelopes address." is included. If it is not defined, all the code under "Get arpeggio envelopes address." is not included. The program becomes at least 10 bytes shorter.

Basically it gives the power to remove the code itself, rather than just not running it. If it's not there, it can't be run ever. Whatever ends up running the code never sees anything related to the if statement. These if statements are information only for the assembler.

Edit: I know a few developers who created if statements using assembler macros, though.

This is an example of macros from asm6 documentation.
Code:
                MACRO setAXY x,y,z
                    LDA #x
                    LDX #y
                    LDY #z
                ENDM

                setAXY $12,$34,$56
                    ;expands to LDA #$12
                    ;           LDX #$34
                    ;           LDY #$56
So you could create a macro like
Code:
MACRO ifgreaterthanunsigned x, y, z
;Macro definition here
ENDM
Where X is the value you want to see if it's greater than Y, and Z is the label where the block ends.
And then in your code you could do
Code:
ifgreaterthanunsigned variable, #2, endblock
;Code to run if variable is > 2
endblock:
But what the macro definition itself would look like, I'd have to do some testing. I'm not sure if/how one can determine the addressing mode of a value given as an argument in an asm6 macro which affects what code would need to be generated by the macro.

You'd need to create a macro for every type of conditional you want, though. (ifgreaterthansigned, ifgreaterthansigned16bit, ifgreaterthanunsigned, ifgreaterthansigned16bit, ifgreaterthanorequalsigned....) etc. (Or maybe add additional parameters to each that allow choosing of the type of the actual comparison.)
 

Mihoshi20

Member
Kasumi said:
That's for conditionally assembling things, Mihoshi20. If the condition is true, the code ends up in your program.If the condition is false,the code does not end in your program.

GGSound uses them to disable features. (This saves space and RAM over having bytes in the program for features the user isn't using.)
Code:
;Get volume envelopes address.
    ldy #0
    lda (sound_param_word_2),y
    sta base_address_volume_envelopes
    iny
    lda (sound_param_word_2),y
    sta base_address_volume_envelopes+1

    ifdef FEATURE_ARPEGGIOS
    ;Get arpeggio envelopes address.
    iny
    lda (sound_param_word_2),y
    sta base_address_arpeggio_envelopes
    iny
    lda (sound_param_word_2),y
    sta base_address_arpeggio_envelopes+1
    endif
If FEATURE_ARPEGGIOS is defined, all the code under "Get arpeggio envelopes address." is included. If it is not defined, all the code under "Get arpeggio envelopes address." is not included. The program becomes at least 10 bytes shorter.

Basically it gives the power to remove the code itself, rather than just not running it. If it's not there, it can't be run ever. Whatever ends up running the code never sees anything related to the if statement. These if statements are information only for the assembler.

Edit: I know a few developers who created if statements using assembler macros, though.

This is an example of macros from asm6 documentation.
Code:
                MACRO setAXY x,y,z
                    LDA #x
                    LDX #y
                    LDY #z
                ENDM

                setAXY $12,$34,$56
                    ;expands to LDA #$12
                    ;           LDX #$34
                    ;           LDY #$56
So you could create a macro like
Code:
MACRO ifgreaterthanunsigned x, y, z
;Macro definition here
ENDM
Where X is the value you want to see if it's greater than Y, and Z is the label where the block ends.
And then in your code you could do
Code:
ifgreaterthanunsigned variable, #2, endblock
;Code to run if variable is > 2
endblock:
But what the macro definition itself would look like, I'd have to do some testing. I'm not sure if/how one can determine the addressing mode of a value given as an argument in an asm6 macro which affects what code would need to be generated by the macro.

You'd need to create a macro for every type of conditional you want, though. (ifgreaterthansigned, ifgreaterthansigned16bit, ifgreaterthanunsigned, ifgreaterthansigned16bit, ifgreaterthanorequalsigned....) etc. (Or maybe add additional parameters to each that allow choosing of the type of the actual comparison.)

The example you chose interesting enough is not the IF/ELSE statement but the IFDEF statement which works differently. You comment on it being an IF statement "If FEATURE_ARPEGGIOS is defined" but that is how IFDEF operates and if you look in the code it is clearly defined as IFDEF " ifdef FEATURE_ARPEGGIOS".

The IF/ELSE feature doesn't check if a symbol or label is defined like IFDEF, it's checking instead for a value or expression and then branching if that value or expression is true. Or thus is my understanding, it may work similar to ifdef allowing one to skip blocks of code by setting a value instead. Like if assembling for different hardware that requires specific setting or items of code that other hardware doesn't need. Like for instance if to include code for the playchoice 10 upper screen or not without having to make two versions of the game separately.
 

Kasumi

New member
I know ifdef is not if, but if still only controls what gets assembled, not what gets run. The asssmbler doesn't really have enough information to create a branch because most values it has can be interpreted as multiple things. The simplest example is signed and unsigned numbers.

The 8bit value -1 is equal to 255. Really!
%11111111
or
$FF
=
-1
If interpreting the value as signed.
%11111111
or
$FF
=
255
If interpreting the value as unsigned.

All 8bit values where the leftmost bit is set can be interpreted as a negative OR a positive number, not just %11111111.
Code:
lda #-1;Assembles as $A9 $FF
lda #255;Also assembles as $A9 $FF
Try it!

The programmer knows whether they want to interpret any given value as positive or negative. They choose the branch they need based on this. The assembler doesn't know and that's why it can't choose branches without more context. Check out this example similar to the above:
Code:
lda #-1;Load the accumulator with -1
cmp #255;Compare to 255
bne label;If the two numbers were not equal, skip
lda #2;Will this line of code run?
label:
Yes, that line of code runs. The value 2 will be in the accumulator at the end of that example. The branch won't happen because %11111111 is identical to %11111111. Knowing this, look at this example:
Code:
lda #-1
sta variable

lda #0
sta variable2

if variable > variable2
     lda #2;Should this run or not?
endif
-1 is not greater than 0.
255 IS greater than 0.

So what code should the assembler generate for the if statement? Should a branch be generated that would skip the load accumulator with 2, or should a branch be generated that doesn't skip the load accumulator with 2? There's not enough information given to figure that out. The above example might seem easy because the store of the negative constant is directly above the if. But imagine if the if was in a subroutine, and 255 and -1 were both stored to variable throughout the program. The assembler would have to guess what the programmer wants.

What ends up happening is that the address for variable is compared with the address for variable2. Those are things that can't change at runtime. Those are things the assembler doesn't have to make a guess for. If the address is greater, lda #2 is added to the program. If the address is not greater, those two bytes aren't added. No branch is generated either way.

Signed vs unsigned is only one of many things that means the context must be guessed. A number can also be 8 bit, or 16bit, or 24bit or 64bit, and you can compare a signed number to an unsigned number or an 8bit number to a 16bit number. This is also why you need multiple macros even for one type of comparison like I mentioned. Is -1 equal to 255? As far as the CPU is concerned, yeah. So if you want 255 to equal 255, but you also want -1 to NOT equal 255 you need macros you can provide the context of the values to. This is why C has you add types to things.
Code:
char variable = -1;
unsigned char variable2 = 255;
Languages without types usually solve the problem in a different way (variables are always signed, and can even be non integers to cover a large range of possibilities), but still usually have a way to force context when it's needed.
 

Mihoshi20

Member
Okay, so then yeah, that function of ASM6 wouldn't be helpful at all since it only matters what gets assembled instead of used for logic in the game itself. Macro or pure asm loops/logic traps it is.
 

darkhog

New member
Okay, so how would I do a switch..case in assembly? Just assembly equivalent of if..else if? Anyway, thanks for all the help, guys (and girls, if any posted here).
 

Kasumi

New member
A switch..case is a jump table in assembly: http://wiki.nesdev.com/w/index.php?title=Jump_table&redirect=no

That link contains several ways. Be aware you can't really sanely do non contiguous cases like you can in things you're probably used to, however.

So if you needed like
Code:
switch(  variable  ){
			case 26:
				
				break;
			case 44:
				
				break;
			case 127:
				
				break;
			case 250:
				
				break;
		}
You're better off just doing if/else. But if you need a more usual
Code:
switch(  variable  ){
			case 0:
				
				break;
			case 1:
				
				break;
			case 2:
				
				break;
			case 3:
				
				break;
		}
You're good.
Note that after jumping to a label from the address table, it's up to each of them to jmp back in order to be exactly like a switch. (But sometimes having them jump back is not desired.)

Edit: Since the article is not totally clear (and in ca65 syntax, I think), here's what the tables would look like:

Code:
; Jumps to the subroutine indexed by 'A'.
do_action:
       asl
       tax
       lda table,x
       sta ptr
       lda table+1,x
       sta ptr+1
       jmp (ptr)
postswitch: 
       rts
      
table:
     .dw case1
    .dw case2
     
case1:
;code for case 1 here
       jmp postswitch
case2:
;code for case 2 here
       jmp postswitch
And here's the faster split table method (without the case labels since that part doesn't change):
Code:
table_lo:
    .dl case1
    .dl case2
table_hi:
    .dh case1
    .dh case2

; Jumps to the subroutine indexed by 'A'.
do_action:
       tax
       lda table_lo,x
       sta ptr
       lda table_hi,x
       sta ptr+1
       jmp (ptr)
postswitch:
Note that these don't check for an out of range case. (An out of range case will likely crash the program)
 
Top Bottom