Page 1 of 2

Need Some Direction

Posted: Fri Jul 07, 2006 10:45 pm
by random
OK, so I got this crazy ambition to write my own NES game (I love the NES and have some light programming experience), problem being I don't know where to go. I basically have this so far:

- Need to learn 6502 ASM
- Need to learn NES hardware, processors, etc.

I've been to nesdev.com, but I don't have any clue as to where I should start. Its like a bunch of documents with no intro page.

Someone really needs to make one...

Can you suggest any documentation I need to consult first (everything seems so abstract right now)?

Many thanks.

Posted: Fri Jul 07, 2006 11:47 pm
by Memblers
The big thing (besides learning 6502 like you mentioned), is understanding the PPU. Try to get an idea of how the name tables and pattern tables work (viewing them in games running in an emulator's debugger can help, tho not many emus display the nametables). I just find that it's a lot more fun when you can actually put results on the screen. But stepping through code in a debugger might help (and you definitely will want to do that, when you have any code doing something you don't expect).

Another thing is to learn how to understand hex and binary numbering.

The programming section of the old NES Tech FAQ has some pretty decent introductory info. http://nesdev.com/NESTechFAQ.htm#programming

There doesn't seem to be really a whole lot of basic info about how to get started with the NES. A lot of it overlaps with doing microprocessor-type stuff in general, and with things like the PPU the really detailed docs are about the really obscure aspects of it (that probably almost none of the original NES game developers knew about, or needed to know about).

I've always thought this doc is good, for reference about the registers, memory map, etc.
http://nesdev.com/ndox200.zip

Posted: Sat Jul 08, 2006 12:25 am
by Bregalad
Learn a bit about tiles, nametables and attribute tables before learning coding by playing with Nesticle or FCEUltra would definitely be a good thing. I remember I got interested in NESdev by playing a lot with FF3 tiles in Nesticle. Nesticle is a totally outdated and innacurate emulator, but it allow the player to learn how the game deals with tiles, and this makes it a worthy emulator for newbies.

Memblers : You definitely have to refresh the NESdev main page. I know it is annoying to refresh a website, my page about Chrono Trigger havent be updated since march 2005 and still show a notice for Chrono Trigger's ten years anniversary, even if the game now have almost 11.5 years. Anyway, I think my page is much less visited than yours.

Posted: Sat Jul 08, 2006 3:40 am
by tepples
Nintendulator, in addition to being several times more accurate than Nesticle, is pretty good for watching pattern tables and nametables nowadays.

Posted: Sat Jul 08, 2006 9:51 am
by Disch
Bregalad wrote:and this makes it a worthy emulator for newbies.
You must never have used FCEUXD


There is no reason to suggest NESticle for debugging over FCEUXD -- FCEUXD has everything. The only thing it lacks is a runtime CHR-ROM editor (though it does have a runtime hex editor).

Posted: Sat Jul 08, 2006 10:35 am
by Bregalad
That was just why I say Nesticle is good to discover the PPU : It allow you to see tiles and edit them. FCEUXD, VirtuaNES and possibly Nintendulator allow pattern table viewing, but the pattern tables are too small to allow someone to really explore the tileset, and you don't get the tile # when clicking on one, and you cannot edit it.

I think FCEUXD is good to for this purpose, but it doesn't really replace Nesticle.

Posted: Sat Jul 08, 2006 10:52 am
by tokumaru
Disch wrote:(though it does have a runtime hex editor)
It has a runtime assembler... that's awesome!

FCEUXD has way more stuff than Nesticle. The only thing Nesticle has that FCEUXD doesn't is the CHR editing, but that is pretty useless when it comes to debuging IMO... you can still easily spot a tile without having to draw an "X" over it...

Anyway, i don't think this is such an inportant feature. beeing able to edit memory is much more usefull for someone to understand how things work.

Posted: Sun Jul 09, 2006 10:03 am
by random
Some questions now...

How big can A be? For instance if I wanted to perform larger (> 8-bits) calculations.

I'm kind of hazy on how 6502 handles signed and unsigned numbers, explanation would be helpful. I'm getting the impression that it only has signed ones.

How do you end a branch? The examples I'm reading go something like this:

lda some_variable
cmp some_other_variable
beq exe_this_code
(continue if false)

exe_this_code
(execute if true)

But what if I want multiple branches?

Seems like I'm asking a lot... Thanks again for answers.

Posted: Sun Jul 09, 2006 11:01 am
by Disch
random wrote:How big can A be?
8-bits. That's all. No more, no less.
For instance if I wanted to perform larger (> 8-bits) calculations.
This is what the C flag (carry flag) in the processor status register is for. for example, if you want to add 2 16-bit numbers together, you'd add the low 8-bits of each together first, and then if the result was >= $100, the C flag will be set, which will allow you to add it into the addition of the high 8-bits of each number.

That explaination wasn't bery good. Remeber that if C is set at the time of ADC, an extra 1 will be added to the sum:

Code: Select all

CLC ; clear carry initially

; add low bytes together
LDA valueA_low
ADC valueB_low
STA sum_low   ; store the sum -- note that if the sum was > $FF, C is set

LDA valueA_hi
ADC valueB_hi
STA sum_hi
Consider you have the following 2 16-bit numbers that you want to add:
$0362
$04F3

first you clear C (CLC) so the extra 1 won't be in your addition... then you add the low bytes of the numbers together:
$62 + $F3 = $55 <-- note that the sum is $55 and not $155 (because it can only be 8-bits). However because it was greater than $FF, C is now set.

So now you add the high bytes together: $03 + $04 = $08 <--- note that it's $08 because C is set by the previous addition, so the extra 1 got added.

therefore... $0362 + $04F3 = $0855.


C works in a similar fashion for multi-byte ASL/ROL/LSR/ROR commands. And works "backwards" for SBC, but the same logic applies.[/quote]

I'm kind of hazy on how 6502 handles signed and unsigned numbers, explanation would be helpful. I'm getting the impression that it only has signed ones.
Well, it doesn't handle them at all really. In fact, if anything I'd say the numbers are all unsigned.

What really makes a number signed or unsigned is how your program treats it. The processor doesn't care one way or the other. $FF can be either 255 or -1.... the processor will always treat it the same regardless -- what matters is how your program works with the number.

There is a "N" flag in the processor status reg, which will be set if the result of the last operation was negative and cleared if the result was positive. All "negative" really means here is "the high bit is set". For this purpose, $00-7F are all "positive" numbers, and $80-FF are all "negative". However, again, the processor doesn't treat them any differently if they're positive or negative.... it kind of treats them all like they're positive ($80 + $80 will give you 256, not -256)

How do you end a branch?
Branches behave just like jumps, only they will only jump under certain conditions. The condition is the current status of a status flag. Usually you will work with BNE, BEQ, BCC, and BCS.

BNE will jump if and only if the Z status flag is clear
BEQ will jump if and only if the Z status flag is set
BCC will jump if C is clear
BCS will jump is C is set

HOW these flags gets set is determined by the instruction(s) before the branch.

Therefore in your examples:
lda some_variable
cmp some_other_variable
beq exe_this_code

that CMP instruction is comparing two values. CMP will set Z if the values equal each other, and will clear Z if the values do not equal each other. If you want to get into details, CMP actually performs a subtraction, and Z is set to the result of that subtraction -- and if the two numbers equal each other, the result is Zero, so Z is set. If the result is nonzero (the numbers don't equal each other), Z will be clear.

Code: Select all

LDA #$03   ; Z=clear
CMP #$03  ; Z=set (3-3 = 0 -- a result of 0 = Z set)
BEQ somewhere  ; jump to 'somewhere' only if Z is set (which it was set by last instruction)

LDA #$00  ; value of $00 = Z set
BNE somewhere  ; this will not jump anywhere because Z is set by above LDA
BEQ somewhere  ; this WILL jump because Z is set by previous LDA
But what if I want multiple branches?
You can branch as many times as you want, there's no limit. Remember that all the branches look at is the status flags, so to really understand branches, you have to understand how the status flags are set.

Posted: Sun Jul 09, 2006 11:22 am
by tokumaru
If you want to check for multiple cases you could do a CMP and a branch for each one. Preferably with the most probable cases first, so that the program doesn't get too slow.

To work with numbers larger than 8 bits, you make use of the carry flag. This code does a 16-bit addition:

Code: Select all

	clc		;clear the carry so that you don't add an unwanted "1"
	lda VarLo	;take the low byte of a variable
	adc #<657	;add the low byte of the number "657"
	sta VarLo	;the low byte is ready to be stored back, but if the result was more than 255, the carry will be set
	lda VarHi	;load the high byte of the variable
	adc #>657	;add the high byte of the number "657" and a possible "1" if there was an overflow in the low byte
	sta VarHi	;store the final high byte
You just have to clear the carry (or set it, if you are subtracting) before adding the first pair of bytes, and then add each of the following pairs at a time, from the least significant one to the most significant one. The carry flag will take care of propagating any carry, and you'll get the correct answer at the end.

The 6502 can handle signed and unsigned numbers. Although there is no difference between them until you perform some sort of calculation or comparison on them. The hex value $FF can be a decimal 255 or a -1. It's up to how you interpret the number.

Addition and subtraction of signed and unsigned numbers is the same, there is not a single difference, but depending on whether you consider the numbers signed or unsigned, you have to interpret the result differently. BPL and BMI are used to check for a negative or positive result, of course, if you are using signed numbers. BCC and BCS can be used to find wich of two unsigned numbers is larger.

When you compare 2 numbers, the rule is: If they are signed, use BPL and BMI, if they are unsigned, use BCC and BCS.

Also, when working with signed numbers larger than 1 byte, only the last (most significant) byte should be considered signed. You could get a little confused with this. I was when I first started studying ASM.

Posted: Sun Jul 09, 2006 1:03 pm
by random
So if I understand correctly, signed and unsigned numbers are interpreted by you and determined with the N and C flags, right?

Pretty much understanding the other stuff. Any source I can get my hands on (probably help a lot in the learning process)?

Thanks for the explanations.

Posted: Sun Jul 09, 2006 1:47 pm
by tepples
tokumaru wrote:If you want to check for multiple cases you could do a CMP and a branch for each one. Preferably with the most probable cases first, so that the program doesn't get too slow.
Or you can divide them in half with BCC/BCS and make what is essentially a binary search tree. Or you can use a jump table like Super Mario Bros., Tetris, and the Apple IIGS ROM do.

Posted: Sun Jul 09, 2006 4:51 pm
by tokumaru
tepples wrote:Or you can divide them in half with BCC/BCS and make what is essentially a binary search tree. Or you can use a jump table like Super Mario Bros., Tetris, and the Apple IIGS ROM do.
I like jump tables myself, but the binary search is a nice option too. I never ran into a case where I needed to perform a huge lot of compares anyway. When I said what I did I was thinking about 3 or 4 cases... simple stuff.
random wrote:So if I understand correctly, signed and unsigned numbers are interpreted by you and determined with the N and C flags, right?
Yes, the processor doen't care whether the numbers are signed or unsined, 8-bit, 16-bit, 24-bit or whatever... it just does the operations it's supposed to and sets the flags (N and C) accordingly. If you are gonna store the results or not, if you are gonna use the flags (and how) or not, it's all up to you.

EDIT: About the source code, http://www.6502.org/ has plenty of general 6502 examples. The main page of NESDEV has some good NES-specific code.

Posted: Sun Jul 09, 2006 5:53 pm
by tepples
tokumaru wrote:I like jump tables myself, but the binary search is a nice option too. I never ran into a case where I needed to perform a huge lot of compares anyway. When I said what I did I was thinking about 3 or 4 cases... simple stuff.
It just called to mind the horror of the CMP linear search that I found in a disassembly of the control-character processing in Higher Text II, a text rendering library for Apple II.

Posted: Sun Jul 09, 2006 9:46 pm
by random
I'm having trouble understanding how the program counter and relative addressing works in relation with JMP and the various branch functions. Any help, is again appreciated.

Many thanks.