Some ASM tutorials in regards to making NES Maker stuff

darkhog
Posts: 252
Joined: Sun May 13, 2018 10:02 am

Some ASM tutorials in regards to making NES Maker stuff

Post by darkhog » Sat Jul 14, 2018 12:21 pm

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
Posts: 26
Joined: Sat Apr 21, 2018 12:43 am
Location: Colorado, USA
Contact:

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by DChilders84 » Sun Jul 15, 2018 4:30 pm

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
Posts: 179
Joined: Wed Mar 07, 2018 2:25 am

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by Bucket Mouse » Sun Jul 15, 2018 7:16 pm

DChilders84 wrote:
Sun Jul 15, 2018 4:30 pm
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
Posts: 252
Joined: Sun May 13, 2018 10:02 am

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by darkhog » Thu Jul 19, 2018 9:02 pm

DChilders84 wrote:
Sun Jul 15, 2018 4:30 pm
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.
User avatar
Kasumi
Posts: 137
Joined: Fri Mar 09, 2018 11:13 pm

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by Kasumi » Fri Jul 20, 2018 4:56 pm

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: Select all

if(variable == 2){
//stuff to do when variable == 2
}

Code: Select all

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: Select all

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

}

Code: Select all

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: Select all

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: Select all

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

}
instead of

Code: Select all

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: Select all

for(int x = 0; x < 0; x++){
//This code would never run because zero is not less than zero
}

Code: Select all

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: Select all

while(variable == 20){
//code here
}

Code: Select all

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: Select all

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: Select all

while(true){
;code here
}

Code: Select all

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.
Last edited by Kasumi on Fri Jul 20, 2018 11:45 pm, edited 3 times in total.
User avatar
MistSonata
Posts: 231
Joined: Fri Feb 16, 2018 7:10 am

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by MistSonata » Fri Jul 20, 2018 7:43 pm

Kasumi wrote:
Fri Jul 20, 2018 4:56 pm
No need to be rude.
Absolutely agree with Kasumi here, Darkhog. There's no need for belittling other users here, we're all just trying to help each other out.
User avatar
dale_coop
Posts: 1269
Joined: Fri Feb 16, 2018 7:05 am
Location: France

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by dale_coop » Fri Jul 20, 2018 8:28 pm

Kashmiri, thank you. Your explanation is clear and very appreciated <3
-----
Sorry about my poor english
All I need: A Damn Fine Cup of Coffee
My games: PRESS START GAME / UNDERGROUND ADVENTURE
User avatar
Mihoshi20
Posts: 417
Joined: Tue Mar 06, 2018 11:47 pm

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by Mihoshi20 » Sat Jul 21, 2018 7:43 am

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: Select all

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
User avatar
Kasumi
Posts: 137
Joined: Fri Mar 09, 2018 11:13 pm

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by Kasumi » Sat Jul 21, 2018 5:21 pm

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: Select all

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

                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: Select all

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: Select all

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.)
User avatar
Mihoshi20
Posts: 417
Joined: Tue Mar 06, 2018 11:47 pm

Re: Some ASM tutorials in regards to making NES Maker stuff

Post by Mihoshi20 » Sat Jul 21, 2018 8:12 pm

Kasumi wrote:
Sat Jul 21, 2018 5:21 pm
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: Select all

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

                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: Select all

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: Select all

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.
Post Reply