Page 2 of 4

Posted: Thu Feb 07, 2008 9:33 pm
by tokumaru
electro wrote:Maybe BEQ to a subroutine that clears the $4015 register?
Remember, you can't branch to a subroutine, ever. This was a very serious mistake in your first attempts to make a NES ROM. The only way to call a subroutine is by using the JSR instruction. You may use the branch instructions to decide if the subroutine will be called or not, but never directly branch to it. The JSR instruction is the only one that puts the current value of the program counter in the stack, so that the subroutine can return to the place where it was called after it finishes.

I know you have no previous programming experience, but keep in mind that computers do not give a damn about english, and it really doesn't know what a subroutine is. This is a word, made up by humans, to make the commands easier to understand and remember. But as far as the CPU is concerned, what each command does is what matters, not what they are called.

For example, when you look at this page, you can see that there is a description of what the JSR instruction actually does, and you can see that "Jump to Subroutine" is just there to help us remember that instruction. By reading that description, you can see that I lied about JSR being the only way to call a subroutine. For some crazy reason, you may want to place a fake return address on the stack, and then simply JMP or branch to the subroutine. When the subroutine ends, it will fetch that address you placed on the stack before, even though it isn't the location where the routine was called. This kind of trick is a bit too advanced for you right now (you don't need to worry about tricking the CPU yet), but this is just an example.

Well, I'm saying all that because, with no programming background, you may end up relying too much on the name of the instructions. So this is just a heads uo, to let you know that computers are much more dumb than you think, and if you master the exact meaning of the instructions you give to them, you'll write better, more reliable, and faster code.

Posted: Fri Feb 08, 2008 10:07 am
by electro
I'm glad you pointed this out to me, looking at the previous code it makes sense now.

Will do some more reading.

Thanks again for that.

T

Posted: Fri Feb 08, 2008 10:47 am
by blargg
A CPU provides the most basic nuts and bolts to build things from. Anything higher level than individual instructions is just a mental aid for us to understand and design programs; the CPU knows nothing about them. Every instruction is a distinct operation with well-defined inputs and effects, nothing more. Put another way, a program of random instructions (note I didn't say random bytes) is just as valid to the CPU as a carefully-coded one, even though the random ones do things that for most purposes would be nonsense.

Posted: Fri Feb 08, 2008 12:07 pm
by never-obsolete
For some crazy reason, you may want to place a fake return address on the stack, and then simply JMP or branch to the subroutine. When the subroutine ends, it will fetch that address you placed on the stack before, even though it isn't the location where the routine was called. This kind of trick is a bit too advanced for you right now (you don't need to worry about tricking the CPU yet), but this is just an example.
this trick is useful if you need a JSR ($aaaa). i've used it for menus and for virtual functions of objects (enemies and whatnot). kinda crazy thinking about oop in assembly.

Code: Select all

	lda CursorCoorY		; get address
	asl
	tay
	lda modeptrs, Y
	sta JmpIndLO
	lda modeptrs + 1, Y
	sta JmpIndHI
	lda >levelEdFuncReturn - 1	; save the return address
	pha
	lda <levelEdFuncReturn - 1
	pha
	jmp (JmpIndLO)
levelEdFuncReturn:
	; go on about business
though i've seen some comercial games push an address to the stack and RTS to it for the same effect.

Posted: Fri Feb 08, 2008 1:10 pm
by blargg
No need to push the new return address, just JSR to a JMP (...) instruction:

Code: Select all

   lda CursorCoorY      ; get address 
   asl 
   tay 
   lda modeptrs,Y 
   sta JmpIndLO 
   lda modeptrs+1,Y 
   sta JmpIndHI 
   jsr jmp_JmpIndLO
   ; go on about business 

jmp_JmpIndLO:
   jmp (JmpIndLO)
And further, you could just push the address on the stack (modeptrs table entries must have 1 subtracted from them):

Code: Select all

   jsr ind_jmp
   ; go on about business 
   
ind_jmp:
   lda CursorCoorY      ; get address 
   asl 
   tay 
   lda modeptrs+1,Y 
   pha
   lda modeptrs,Y 
   pha
   rts

modeptrs:
   .word first-1, second-1 ...
Finally, if you split the table, you can eliminate more:

Code: Select all

   jsr ind_jmp
   ; go on about business 
   
ind_jmp:
   ldy CursorCoorY      ; get address 
   lda modeptrs_hi,Y 
   pha
   lda modeptrs_lo,Y 
   pha
   rts

modeptrs_lo:
   .byte <(first-1), <(second-1) ...
modeptrs_hi:
   .byte >(first-1), >(second-1) ...

Posted: Mon Feb 25, 2008 4:27 pm
by Laserbeak43
have only gotten through page one, but this is a beautiful thread so far!! love it!

but i do have one question(for now):

Code: Select all

Clear_Sound: 
   sta $4000,x      ; store accumulator at $4000 offset by x 
   inx         ; increment x 
   cpx #$0F      ; compare x to $0F 
   bne Clear_Sound      ; branch back to Clear_Sound if x != $0F 

what is the point of clearing the sound if you want x to equal $0F?
is that some kind of shift/mask trick?

Posted: Mon Feb 25, 2008 4:59 pm
by blargg
That code writes register A to memory locations $4000 through $400E. Here are comments describing the instructions. This is really basic stuff. Don't skip the basics in your learning process.

Code: Select all

Clear_Sound: 
   sta $4000,x      ; Store A at memory location $4000+X
   inx              ; X = X + 1
   cpx #$0F         ; If X is not equal to $0F, jump to Clear_Sound
   bne Clear_Sound

Posted: Mon Feb 25, 2008 5:08 pm
by electro
Laserbeak43 wrote:have only gotten through page one, but this is a beautiful thread so far!! love it!

but i do have one question(for now):

Code: Select all

Clear_Sound: 
   sta $4000,x      ; store accumulator at $4000 offset by x 
   inx         ; increment x 
   cpx #$0F      ; compare x to $0F 
   bne Clear_Sound      ; branch back to Clear_Sound if x != $0F 

what is the point of clearing the sound if you want x to equal $0F?
is that some kind of shift/mask trick?
Ditto. I never learned so much from a thread in any other forum before.

T

Posted: Mon Feb 25, 2008 6:14 pm
by Laserbeak43
blargg wrote:That code writes register A to memory locations $4000 through $400E. Here are comments describing the instructions. This is really basic stuff. Don't skip the basics in your learning process.

Code: Select all

Clear_Sound: 
   sta $4000,x      ; Store A at memory location $4000+X
   inx              ; X = X + 1
   cpx #$0F         ; If X is not equal to $0F, jump to Clear_Sound
   bne Clear_Sound
it's more the other way around for me. i learn more in a community. always have.
but anyway, it's true. learn the basics. but this still doesn't explain to me why you want to jump to Clear_Sound if x is NOT equal to $0F. if it is does this mean it IS clear? cause i'm under the impression that clear would be $00.

Posted: Mon Feb 25, 2008 6:25 pm
by tepples
It's trying to clear (set to zero) all registers from $4000 through $400E. Once all these registers have been cleared, X will equal $0F, and the loop ends.

Posted: Mon Feb 25, 2008 8:42 pm
by Laserbeak43
tepples wrote:It's trying to clear (set to zero) all registers from $4000 through $400E. Once all these registers have been cleared, X will equal $0F, and the loop ends.
oh! duh! x serves ONLY as a counter. i was expecting it to do more. lol. i tend to make things more complicated than they are! sorry :P and thanks XD

Posted: Mon Feb 25, 2008 9:26 pm
by blargg
The best way to understand code is to run it in your mind, or with a simulator if you have one. Then you can see the big picture. For example, this is clearly a loop:

Code: Select all

lda #0              ; A = $00
ldx #0              ; X = $00
Clear_Sound: 
sta $4000,x         ; Store 0 at memory location $4000
inx                 ; X = $01
cpx #$0F
bne Clear_Sound     ; X is not equal to $0F, so this is taken
Clear_Sound: 
sta $4000,x         ; Store 0 at memory location $4001
inx                 ; X = $02
cpx #$0F
bne Clear_Sound     ; X is not equal to $0F, so this is taken
...
sta $4000,x         ; Store 0 at memory location $400E
inx                 ; X = $0F
cpx #$0F
bne Clear_Sound     ; X is equal to $0F, so this is NOT taken
; done
Loops are useful when you need to process a lot of values, where doing each manually would take lots of code. The combination of conditional branches (decisions) and loops can allow a lot of functionality with a small amount of code. Loops are also useful when the number of values processed depends on some value while the program is running, rather than a fixed value as the above loop does.

Posted: Tue Feb 26, 2008 6:20 am
by Laserbeak43
yeah i'm quite familiar with loops. just not in asm :lol: which actually looks a lot simpler!! maybe that's why i've missed it? oh well. as always, thanks for the explanation! :D

Posted: Tue Feb 26, 2008 8:06 am
by electro
That's a great explanation of the loop that clears the sound registers.

This is awesome.

Thanks.

T

P.S. I meant to say above that I have never learned so much in a forum before. I had to edit my original comment, I think the way I worded it, it sounded the opposite of what I meant!

Posted: Tue Feb 26, 2008 8:44 am
by tepples
tokumaru wrote:
electro wrote:Maybe BEQ to a subroutine that clears the $4015 register?
Remember, you can't branch to a subroutine, ever.
Ever?