It is currently Tue Jul 25, 2017 9:52 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sat Mar 11, 2017 11:25 am 
Offline
User avatar

Joined: Wed Apr 07, 2010 1:14 am
Posts: 463
Location: Iran
Code:
;NES Programming Tutorial
;Level 4 : Init Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;iNES header data (16bytes)
;32KB PRG + 8KB CHR + NROM-256 + Vertical Mirroring
  .db $4E,$45,$53,$1A,$02,$01,$01,$00
  .db $00,$00,$00,$00,$00,$00,$00,$00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;PRG codes $8000 ~ $FFFF (32KB)
  .base $8000

RESET:
   SEI
   CLD

;Turn off NMI and rendering
   LDA #%00000000
   STA $2000
   LDA #%00000000
   STA $2001

;PPU warm up
   LDA $2002
vBlank_wait1:
   BIT $2002
   BPL vBlank_wait1
vBlank_wait2:
   BIT $2002
   BPL vBlank_wait2

;Clear RAM
   LDA #$00
   LDX #$00
clear_loop:
   STA $0000, X
   STA $0100, X
   STA $0200, X
   STA $0300, X
   STA $0400, X
   STA $0500, X
   STA $0600, X
   STA $0700, X
   INX
   CPX #$00
   BNE clear_loop

;Turn on NMI and rendering
   LDA #%00000000
   STA $2000
   LDA #%10000000
   STA $2001

;Infinite loop
Forever:
   JMP Forever
;---------------------------;
NMI:
   RTI
;---------------------------;
IRQ:
   RTI
;---------------------------;
  .pad $FFFA,$FF
;Vectors
  .org $FFFA
  .dw NMI
  .dw RESET
  .dw IRQ
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;CHR data $0000 ~ $1FFF (8KB)
  .base $0000
  ;graphic data
  .pad $2000,$FF


Output :

Image


Explanation :

* SEI stands for SEt the Interrupt-disable bit, we use it to disable IRQ interrupt.

* CLD stands for CLear Decimal, NES cpu doesn't have Decimal mode at all but we use CLD to make sure it is disabled!

* $ is a prefix for address in hexadecimal system starting from $0000 to $FFFF

* #$ is a prefix for immediate value in hexadecimal system starting from #$00 to #$FF

* #% is a prefix for immediate value in binary system starting from #%00000000 to #$11111111

* The rightmost bit is called bit 0 or lsb (least significant bit)

* The leftmost bit is called bit 7 or msb (most significant bit)

* So the name of the bits are like this : bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

* LDA stands for LoaD Accumulator. Accumulator is a register of CPU, it is like a variable. It can hold max 8bit values starting from #$00 to #$FF

* STA stands for STore Accumulator. We can write value of Accumulator into an address

* PPU stands for Picture Processing Unit

* Some special addresses are called Port. CPU use Ports to control other parts like PPU, Joypads, etc

* Most of the Ports are bit wise. It means making their bit 1 or 0 have a special effect and meaning.

* $2000 and $2001 are PPU ports.

Code:
   LDA #%00000000
   STA $2000


* With this code we Load the value #%00000000 ( = #$00) into A and then we write A to $2000. Setting the 7th bit to 0 stops NMI Interrupt from happening.

Code:
   
   LDA #%00000000
   STA $2001


* We set the 3rd and 4th bits to 0 to hid the graphics. At start up there is nothing to show, right?

* $2002 is another PPU port. We can only read its value. It gives report about PPU. If 7th bit is 1 then it shows that vBlank is happening. Reading this port clears 7th bit and will stay 0 until the start of a new vBlank

* To find out the meaning of other bits of $2000, $2001, $2002 read this : PPU registers

* There are different kinds of Flags : Carry, Zero, Interrupt, Decimal, Overflow, Sign

* Executing an opcodes can set or clear these flags

* These flags together are called Processor Status

* Need more info about flags? then read this : CPU status flag behavior

* After each operation there is a result value. The 7th bit of the result is copied to SIGN FLAG. 0 shows positive result and 1 shows negative result

* BIT checks the 7th bit of $2002. If it is 0 vBlank is not happening. If it is 1 then vBlank is happening. SIGN FLAG gets the value

* BPL stands for Branch on PLus (positive sign). If SIGN FLAG is set to 0 then a jump will happen to its following label. It will jump until the SIGN FLAG is set to 1.

* ;PPU warm up : This loop runs until a new vBlank starts. We have to wait at least 2 vBlanks before using PPU. It needs time to become stabilized.

* LDX is similar to LDA. X is another register like A

* STA $0000, X : it is similar to this : A --> ($0000 + X). For example if the value of X is #$44, then the value of A will go into $0044.

* INX stands for INcrement X. For example if the value of X is #$44 then it will be #$45

* X can hold up to 8bit values, let's say X is #$FF, after INX it becomes #$00

* CPX stands for ComPare X register. It compares the value of X with its following value

* BNE stands for Branch on Not Equal. If compared values are not equal to each other then it will jump to its following label

* From $0000 to $07FF is 2KB RAM. We can store values like score, life, etc here to use later. At start up we have to clear the whole RAM

Code:
   LDA #%10000000
   STA $2001


* With this code we set the 7th bit of $2001 to 1, and by doing so the whole screen becomes blue!

* Here is a more correct and optimized init code

/////////////////////////////////////////////////////////////////////////////////////////////////

How to use Debugger :

* Sometimes the game doesn't work as we expect, maybe because of a logical mistake in the source code.

* So how to find it? We need to use a tool called Debugger. We can run the game step by step and check the codes to find the error.

* In the above code the RESET label is under $8000, it means the game will run from that address. So let's use debugger from that address and find out what is going on!

* Run the game with FCEUX emulator

* Go to Debug > Debugger

* Click Add

* Type 8000 in the address box, click on Execute option, click ok

* This is called a breakpoint and it means when CPU want to read or write or execute a special address then stop the game and use debugger to run the game step by step.

* Go to NES > Reset

* Game stops running and debugger windows comes up

* On the first line it shows the instruction which is going to be executed

Code:
00:8000:78        SEI


* Click on step into to run the instructions one by one

Image

/////////////////////////////////////////////////////////////////////////////////////////////////

How to disassemble :

* An assembler (ASM6) converts the source code of assembly language to object code of machine language : Game.asm --> Game.nes

* A dissembler does the opposite : Game.nes --> Game.asm

* Disassembling is not accurate all the times because of some difficulties, for example it is not easy to distinguish between opcodes and data.

* The best disassembler that I found so far is : NESrevPlus

* It is easy to use : load your game.nes into it then save game.asm :

Image

* Compare the original source code with disassembled one.

* Wow today was awesome, wasn't it?

/////////////////////////////////////////////////////////////////////////////////////////////////

Exercise :

Change the background color to Red.

/////////////////////////////////////////////////////////////////////////////////////////////////

Files :
asm6.exe
Assembler.bat
Game.asm

/////////////////////////////////////////////////////////////////////////////////////////////////

Former Level : NES Programming Tutorial : Interrupts
Next Level : NES Programming Tutorial : Background


Last edited by FARID on Fri Mar 17, 2017 11:02 pm, edited 31 times in total.

Top
 Profile  
 
PostPosted: Sat Mar 11, 2017 12:51 pm 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1641
Location: DIGDUG
I am usually setting 2001 to $1e to enable normal rendering. $80 would set the blue emphasis bit, but leave rendering off.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Sat Mar 11, 2017 1:12 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5431
Location: Canada
You should do an extra BIT $2002 before vBlank_wait1. The system can be reset in the middle of vblank with the vblank flag already set, which would make the first wait return immediately, and you really do need to wait the full 2 frames for the PPU to warm up before you can start using it. Writes to it may fail to have any effect until that point. Nerdy Nights' tutorial fails to do this, and even SMB fails to do it (but luckily the rest of its startup before it uses the PPU takes just long enough for the PPU to warm.)

So, an extra throw-away BIT $2002 before you start the wait loop will clear the bit just in case it's already inside a vblank period. (It's like a 10% chance that any reset is inside vblank, I think?) the vblank flag was spuriously left on at power on by the PPU (see wiki).

Also you can put the RAM clear in between the two vblank waits just to speed things up if you like. (Not too important, just a common practice.)

Edit: accidentally wrote $2000 instead of $2002.
Edit: clarified the real need for extra $2002 clear at init.


Last edited by rainwarrior on Mon Mar 13, 2017 2:31 pm, edited 2 times in total.

Top
 Profile  
 
PostPosted: Sat Mar 11, 2017 1:57 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18673
Location: NE Indiana, USA (NTSC)
Typo: bit $2002, as reading $2000 instead returns the content of the PPU's I/O data latch and has no side effects. Fixed

Probability of reset in vblank is 20 in 262 on NTSC, 20 in 312 on Dendy, and 70 in 312 on PAL NES.


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 3:39 am 
Offline

Joined: Tue May 28, 2013 5:49 am
Posts: 631
Location: Sweden
Also before the PPU warm up I think you may need to write $40 to $4017 (disables APU frame IRQ) and clear $4010 (disables DMC IRQ) in addition to clearing $2000 and $2001 that you already do.


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 9:31 am 
Offline
User avatar

Joined: Wed Apr 07, 2010 1:14 am
Posts: 463
Location: Iran
dougeff wrote:
I am usually setting 2001 to $1e to enable normal rendering. $80 would set the blue emphasis bit, but leave rendering off.

In what way it is better?

@ rainwarrior
Thanks for the suggestion. I used LDA $2002, is that ok?

Pokun wrote:
Also before the PPU warm up I think you may need to write $40 to $4017 (disables APU frame IRQ) and clear $4010 (disables DMC IRQ) in addition to clearing $2000 and $2001 that you already do.


I used SEI which disables IRQ interrupt, do I really need to mess up with $4010 and $4017 really?
I do not want to confuse beginners with unnecessary codes, they can learn about them later, right?

Garth wrote:
SEI is part of the reset sequence the processor carries out; so the only reason to have it in the code is if you might ever want to jump to it without a hardware reset pulse.

The CLD in your reset routine is needed for NMOS 6502's; but on the CMOS 65c02, it's an automatic part of the reset sequence the processor carries out. Perhaps all NES computers used the older NMOS one. You would definitely know better than I, since although I'm a 6502 enthusiast, I'm not a games person. I have an article on the many differences between the NMOS and the CMOS, at http://wilsonminesco.com/NMOS-CMOSdif/ .

In the "turn off screen" portion, since you're writing 0 to both $2000 and $2001, there's no need to re-load it; so one of the LDA #%00000000 lines can be deleted. The accumulator still has 0 in it. The CMOS 6502 has the STZ (STore Zero) instruction, so it wouldn't matter what's in the accumulator anyway, and the first LDA could be omitted too.

After "Clear RAM," the LDX# could be replaced with TAX to save a byte, since the same value is already in A.

After INX, the CPX #$00 is unnecessary, because the INX includes an automatic, imiplied compare to zero already.

If you're not using either of the interrupts, you can just put the two labels at one RTI, saving one RTI. IOW, both vectors will go to the same RTI byte.


I (and probably most of other beginners) prefer to use explicit style of coding and easy to read, even if it eats a lot of space!


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 9:58 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5431
Location: Canada
LDA is fine, I just tend to use BIT out of habit because of its strong association with $2002.

SEI masks interrupts, but writing to $4010 and $4017 will disable and acknowledge any pending interrupts from those devices. I think it's good practice to write to them at init. If you ever CLI to start using interrupts you don't want to have an unacknowledged signal there that's been hanging around since reset, because it would immediately trigger the IRQ.

Also, you forgot to initialize the stack pointer! (LDX #$FF, TXS) An uninitialized stack doesn't matter in some naive cases, but there are lots of reasons why it does matter (e.g. debugging, or trying to reclaim some extra memory from the unused stack area.)

I don't really understand the "show screen" part of the code. There's no reason to write 0 to $2000 again, and you haven't initialized the screen with any data. You're using $2001 to show a tint on the screen, but you haven't initialized the palette, so on a real machine the colour might be more or less random anyway. Normally we would write at least the palettes and a blank nametable (and the scroll registers) before showing anything. Perhaps at least setting the "greyscale" bit in $2001 as well would insure that the uninitialized palette would be a non-black grey?


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 10:08 am 
Offline
User avatar

Joined: Wed Apr 07, 2010 1:14 am
Posts: 463
Location: Iran
rainwarrior wrote:
LDA is fine, I just tend to use BIT out of habit because of its strong association with $2002.

SEI masks interrupts, but writing to $4010 and $4017 will disable and acknowledge any pending interrupts from those devices. I think it's good practice to write to them at init. If you ever CLI to start using interrupts you don't want to have an unacknowledged signal there that's been hanging around since reset, because it would immediately trigger the IRQ.

Also, you forgot to initialize the stack pointer! (LDX #$FF, TXS) An uninitialized stack doesn't matter in some naive cases, but there are lots of reasons why it does matter (e.g. debugging, or trying to reclaim some extra memory from the unused stack area.)


I didn't forget them, I omit them on propose because I want to make the tutorial as simple as possible and step by step.
If I write those code then I have to teach about them, while for this level I didn't use APU and Stack, right?
In each level I try to make the code easy to understand, I don't want to make a perfect and optimized code!


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 11:29 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1641
Location: DIGDUG
Quote:
Storing $80 to $2001

In what way it is better?


It's not better. It's what YOU are doing.

Storing $1e to $2001 is better, in the sense that it matches your comment...

Code:
;Show screen

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Sun Mar 12, 2017 10:08 pm 
Offline
User avatar

Joined: Wed Apr 07, 2010 1:14 am
Posts: 463
Location: Iran
dougeff wrote:

FARID wrote:
I (and probably most of other beginners) prefer to use explicit style of coding and easy to read, even if it eats a lot of space!


Let me recommend getting into the habit of being more efficient and concise right from the start. I've done projects that took two inches of fanfold paper to print (in spite of a constant efforts to make the assembler's output fit in the available memory), but I can tell you that even on much much smaller program it'still too easy to lose control of the project if you don't work at conciseness. You'll start having problems you don't understand and you can't figure out what you did and how to conquer them.

Many times, I've had to take someone else's troubled code and figure out what the problem is. These are usually only a page or two. To make it more understandable, I always start by doing kind of like reducing an equation to lowest terms. Part of that is to weed out the unnecessary instructions, like a CPX #0 after DEX. The process makes the code more manageable, and I often end up finding the problem in that first process.


FARID wrote:
Explanation :

* SEI stands for SEt Interrupt, we use it to disable IRQ interrupt.


It stands for "SEt the Interrupt-disable bit."


FARID wrote:
* $ is a prefix for address in hexadecimal system starting from $0000 to $FFFF

* #$ is a prefix for values in hexadecimal system starting from #$00 to #$FF


The $ just means it's in hex. The # means "immediate." ASM6 is not one of the assemblers I've used; but I seriously doubt that it breaks away from this standard. "Immediate" means that you're going to load the operand itself into the register. So for example, LDA #$40 means you're going to put the value $40 into the accumulator. LDA $40 (without the #) means you're going to read address $40 in zero page to find out what to put in the accumulator.

The 65816 is the 16-bit step up from the 6502, and you could have LDA #$4BA6 for example. The # means you're going to put that exact value in the accumulator. Without the #, it would mean you'll load the accumulator with whatever data you read at address $4BA6 of the current data bank. The # is not related to the number of bits, but to where the data is coming from.

FARID wrote:
* The rightmost bit is called bit 0 or LSB (Least significant bit)

* The leftmost bit is called bit 7 or MSB (Most significant bit)


Usually MSB and LSB, in cap.s, stand for "most-significant byte" and "least-significant byte," whereas in lower-case they mean "most-significant bit" and "least-significant bit." It will be good to stick to this convention.


FARID wrote:
* LDX is similar to LDA. X is another register like A


Note the different functions though. X can be used for indexing (unlike A), whereas A can be used for arithmetic and logic operations (unlike X, except that X can be incremented and decremented).


FARID wrote:
* STA $0000, X : For example if the value of X is #$44, then the value of A will go into $0044


This will work, but in this case you would want to do STA $00,X to make sure the assembler produces the faster, shorter sequence 95 00 rather than 9D 00 00.

FARID wrote:
* BNE stands for Branch on Not Equal. If compared values are not equal to each other then it will jump to its following label


Note that It could also be considered a "BNZ" or "Branch if Not Zero;" because many instructions have an automatic, implied compare-to-zero instruction built in, including LDA, LDX, LDY, INC, INX, INY, DEC, DEX, DEY, AND, ORA, EOR, ASL, LSR, ROL, ROR, PLA, SBC, ADC, TAX, TXA, TAY, TYA, and TSX, plus a few more with the CMOS 65c02: INA, DEA, PLX, and PLY. The comparison instructions do a subtraction. Although the numerical result is not kept, the Z flag is set if the subtraction result is zero, meaning the two numbers were equal; otherwise it is cleared.


Thank you


Top
 Profile  
 
PostPosted: Mon Mar 13, 2017 10:13 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1641
Location: DIGDUG
I, for one, am enjoying this discussion. My own example code, and major projects only do 2 V-blank checks before writing to PPU.

would a back to back check work? I seem to remember that checking 2002 clears the flag, so it seems like it would wait twice, if I...

Code:
-
 bit $2002
 bpl -
-
 bit $2002
 bpl -

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon Mar 13, 2017 10:46 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18673
Location: NE Indiana, USA (NTSC)
You have to wait at least one full frame period.

If the player pressed the Reset button during vblank, the first loop will exit immediately because nothing has cleared the vblank flag. (Only the start of the pre-render line and reading $2002 clear it.) Then the second loop will exit at the start of the first vblank, but the PPU will still be unresponsive to writes until after the first vblank.


Top
 Profile  
 
PostPosted: Mon Mar 13, 2017 10:56 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 1641
Location: DIGDUG
Sorry, I mean, these 2 v-blank checks, then another one I didn't mention, a little later.

Would this example count as 2? Assuming you weren't already in v-blank.

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Last edited by dougeff on Mon Mar 13, 2017 11:01 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Mon Mar 13, 2017 11:00 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 18673
Location: NE Indiana, USA (NTSC)
If you have these two loops and then burn at least enough cycles to finish vblank (2387 on NTSC and Dendy, 7566 on PAL NES) before writing to the PPU, it'll work. If you have these two loops and then a third loop later on before writing to the PPU, it'll work.


Top
 Profile  
 
PostPosted: Mon Mar 13, 2017 2:28 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5431
Location: Canada
rainwarrior wrote:
It's like a 10% chance that any reset is inside vblank, I think?

I think I may have muddied the waters slightly with this comment, so just to clarify...

I'm used to adding an extra BIT $2002 out of habit before I enter a BIT $2002 / BPL wait loop, because often that code can get entered mid-vblank, so it's necessary in any generic wait for vblank.

At reset, though, you still should have it but I think the reasoning is different. The PPU will power/reset consistently at the start of a frame, but the vblank bit of $2002 is random/unreliable (according to the wiki). So, the problem isn't that it might start in vblank, it's that $2002 might misreport a non-existent vblank before the first true vblank occurs, causing you to skip the first frame of wait if you don't clear it once before you start your loop.

On the Famicom or top-loader, a CPU reset might occur at any time during the frame but the PPU does not reset, so technically the reset could start you in the middle of vblank, but in this case the PPU is already warm so it's not an issue.


...or maybe this is just a semantics thing, because the "unreliable" vblank flag is probably caused by resetting during vblank?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group