Adapting a NESASM tutorial for ASM6

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
noattack
Posts: 147
Joined: Tue Feb 13, 2007 9:02 pm
Location: Richmond, VA

Adapting a NESASM tutorial for ASM6

Post by noattack »

Just getting my feet wet, so bear with me...

As the title suggests, I was attempting to translate bunny's first NESASM tutorial into ASM6 syntax. After rearranging and fiddling a bit, I was able to get my source to compile, but it doesn't produce the proper output. More specifically, it produces a blank gray screen in the emulator (it should be blue). The tutorial is basically flipping an emphasis bit to change the background color.

Here's my source:

Code: Select all


; iNES identifier
.byte "NES",$1a
.byte $01 ; 1 PRG-ROM block
.byte $01 ; 1 CHR-ROM block
.byte $00 ; unsure about these...which is mapper?
.byte $00 ;

;;;;;;;;;;;;;;

  .org $0000
  .incbin mario.chr   ;includes 8KB graphics file from SMB1

;;;;;;;;;;;;;;

  .org $C000
RESET:
  SEI          ; disable IRQs
  CLD          ; disable decimal mode
  LDX #$40
  STX $4017    ; disable APU frame IRQ
  LDX #$FF
  TXS          ; Set up stack
  INX          ; now X = 0
  STX $2000    ; disable NMI
  STX $2001    ; disable rendering
  STX $4010    ; disable DMC IRQs

vblankwait1:       ; First wait for vblank to make sure PPU is ready
  BIT $2002
  BPL vblankwait1

clrmem:
  LDA #$00
  STA $0000, x
  STA $0100, x
  STA $0200, x
  STA $0400, x
  STA $0500, x
  STA $0600, x
  STA $0700, x
  LDA #$FE
  STA $0300, x
  INX
  BNE clrmem

vblankwait2:      ; Second wait for vblank, PPU is ready after this
  BIT $2002
  BPL vblankwait2

  LDA #%01000000   ;intensify emphasis bit
  STA $2001

Forever:
  JMP Forever     ;jump back to Forever, infinite loop

NMI:
  RTI

  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the
                 ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                 ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial


So I'm not sure what's going wrong here. I omitted the 'bank' directives, as I understand that ASM6 doesn't require them. The header was cobbled together from a different tutorial (which also explains why chr data is there as well). I'm not sure how this should look in ASM6. The source also wouldn't compile until I put my .org statements in order.

This is all elementary, but where am I screwing up?
User avatar
beneficii
Posts: 127
Joined: Tue Jul 12, 2005 4:37 pm

Post by beneficii »

You have the .orgs and ordering messed up. First, the .incbin for the CHR-ROM should go at the end, after the PRG-ROM, just the same as the order in the actual ROM itself. (Remember, ASM6 isn't designed exclusively for the NES/Famicom.) Get rid of the .org statement before the .incbin.

The remaining .org statement ".org $C000" should be fine, though you can also replace it with "$ = $C000" in ASM6. The ".org $FFFA" should also be fine, because the second and subsequent .ORG statements are treated like .PAD statements in ASM6. (In ASM6, I recommend using a combination of "$ = addr" and ".PAD addr" to control the addressing, because that allows you good control over the ROM.)

With these changes, try assembling the ROM again.

EDIT: Also, the iNES header needs to be exactly 16 bytes long. Pad the end with 0's if needed be. I would recommend, before the header, putting "$ = $0000", then after the header putting ".PAD 16, 0." Then, before the code, putting "$ = $C000". That should do the program fine.

EDIT 2: It might be safe to put ".pad $FFFA" to replace the ".org $FFFA" if it's still not working properly, fixing the possible error of the program treating the ".org $FFFA" as the first .org statement, treating it like the "$ = $FFFA" statement. Or you could just follow loopy's advice.
Last edited by beneficii on Mon Feb 02, 2009 10:27 pm, edited 2 times in total.
User avatar
loopy
Posts: 403
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Re: Adapting a NESASM tutorial for ASM6

Post by loopy »

Header should be 16 bytes, and chr data should be moved to the end.

Code: Select all


; iNES identifier
.byte "NES",$1a
.byte $01 ; 1 PRG-ROM block
.byte $01 ; 1 CHR-ROM block
.byte $00 ; unsure about these...which is mapper?
.byte $00 ;
.byte 0,0,0,0,0,0,0,0  ; pad header to 16 bytes

;;;;;;;;;;;;;;

  .org $C000
RESET:

  ......
  ......
  ......

NMI:
  RTI

  .org $FFFA     ;first of the three vectors starts here
  .dw NMI        ;when an NMI happens (once per frame if enabled) the
                 ;processor will jump to the label NMI:
  .dw RESET      ;when the processor first turns on or is reset, it will jump
                 ;to the label RESET:
  .dw 0          ;external interrupt IRQ is not used in this tutorial


;;;;;;;;;;;;;;

  .incbin mario.chr   ;includes 8KB graphics file from SMB1

User avatar
noattack
Posts: 147
Joined: Tue Feb 13, 2007 9:02 pm
Location: Richmond, VA

Post by noattack »

Thank you both. Everything is working properly now.

So, if I understand correctly, an .ORG statement should only be used once, since it 'sets the starting address'? Or not at all, since it seems easier to use the "$ = ADDR" format instead.

.PAD, on the other hand, fills a particular memory location, rather than moving to that address in the actual execution of code, right?
User avatar
beneficii
Posts: 127
Joined: Tue Jul 12, 2005 4:37 pm

Post by beneficii »

noattack:

Basically, when you set a label like "label:", when the assembler reaches that label definition, it will assign an address to it based on the current ROM address it calculates. When you set ".org addr" for the first time or "$ = addr", it tells the assembler what address to start counting from as it assembles each instruction and each byte. If an instruction takes up 3 bytes in the ROM, then it counts 3 bytes from where it was. Doing "$ = addr" again simply tells the assembler to start counting from that new address.

".org addr" the second or subsequent times or ".pad addr" will cause the assembler to fill bytes from the address the .org or .pad statement is at, counting each byte along the way, until it reaches the address specified as "addr".
User avatar
loopy
Posts: 403
Joined: Sun Sep 19, 2004 10:52 pm
Location: UT

Post by loopy »

noattack wrote:Thank you both. Everything is working properly now.

So, if I understand correctly, an .ORG statement should only be used once, since it 'sets the starting address'? Or not at all, since it seems easier to use the "$ = ADDR" format instead.
Yes, use it once at the beginning of your code to set the starting address. You'll see it multiple times in the same NESASM code, to set the address of each bank. I would keep using .ORG since it's the standard way of doing things and understood by everyone, whereas the "$=addr" thing is specific to ASM6.

Regarding .ORG, perhaps I should mention ASM6 diverges from other assemblers in another way - it allows you to output data before an origin is set, where other assemblers might complain or assume a default value. This is only allowed in cases where an ambiguous address doesn't matter.

Code: Select all

; iNES identifier 
.byte "NES",$1a 
.byte $01 ; 1 PRG-ROM block 
.byte $01 ; 1 CHR-ROM block 
.byte $00 ; unsure about these...which is mapper? 
.byte $00 ; 
.byte 0,0,0,0,0,0,0,0  ; pad header to 16 bytes 

;;;;;;;;;;;;;; 

  .org $C000 
RESET:
   SEI
With your code here for example, it outputs 16 header bytes, then starts assembling at $C000. With another assembler, you might be expected to have ".org $C000-16" before the header.

Code: Select all

foo:    .byte 0
        .org $8000
        lda foo
This, on the other hand, does make ASM6 throw an error since it doesn't know where foo is located. (I suppose I could be clever and make it infer an origin of $7fff, but I digress...)
noattack wrote:.PAD, on the other hand, fills a particular memory location, rather than moving to that address in the actual execution of code, right?
.PAD fills memory UP TO a particular address.
User avatar
noattack
Posts: 147
Joined: Tue Feb 13, 2007 9:02 pm
Location: Richmond, VA

Post by noattack »

So in the following snippet:

Code: Select all

  
.org $C000 
RESET: 

  ...... 
  ...... 
  ...... 

NMI: 
  RTI 

  .pad $FFFA     ;first of the three vectors starts here 
  .dw NMI        ;when an NMI happens (once per frame if enabled) the 
                 ;processor will jump to the label NMI: 
  .dw RESET      ;when the processor first turns on or is reset, it will jump 
                 ;to the label RESET: 
  .dw 0          ;external interrupt IRQ is not used in this tutorial 


the initial .ORG sets the ROM address at $C000, then executes the code directly following. Then the .PAD tells the assembler to fill all bytes past the code up to address $FFFA, at which point the three vectors are defined?

And then even if I had a second .ORG in lieu of the .PAD, ASM6 assembles as if it were the latter, since it is the second .ORG in the source.

I appreciate the help. It's taking a while to wrap my brain around the concept of addressing.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

noattack wrote: the initial .ORG sets the ROM address at $C000, then executes the code directly following. Then the .PAD tells the assembler to fill all bytes past the code up to address $FFFA, at which point the three vectors are defined?
Correct
And then even if I had a second .ORG in lieu of the .PAD, ASM6 assembles as if it were the latter, since it is the second .ORG in the source.
The best way to visualize this is to look at the assembled file in a hex editor. Given the following example:

Code: Select all

.org $C000
foo:
  LDA #1

.pad $C008
bar:
  LDA #2
This would Assemble to the following:

Code: Select all

A9 01 00 00 00 00 00 00 A9 02
If you replace that .pad with an .org, you'd get the following:

Code: Select all

A9 01 A9 02
However in both cases... 'foo' would be a constant in the assembler representing the address $C000 -- and 'bar' would be $C008.

The reason you need to pad is because label names and .org statements are simply for the assembler's use and are not assembled into the final binary. Once your ROM is assembled, all the data is just clumped together in a mesh. The emulator (or NES) will take the data as-is and stick it in ROM addressing space (in this case, $C000 since you only have 1 PRG bank).

The latter example without the .pad would result in the bytes "A9 01 A9 02" appearing at address $C000 in an emulator, which means the code in the 'bar' label is really at address $C002. However because you told the assembler to .org over to $C008, all the references to that label in your code will jump to address $C008 instead of $C002.

This is a pretty long winded explanation. Hopefully I didn't confuse you more than help you. Heh. The key thing to remember is that the NES doesn't see any of this .org/.pad crap.. that stuff just configures the assembler to assemble the code a certain way. You need to get your code to match the file format accepted by the NES and emulators. In this case, that means exactly $4000 bytes of data, starting at address $C000. With the last 6 bytes (address $FFFA) being the vector table. To accomplish this, you need to fill/pad unused space between $C000-FFFA with zeros, otherwise the vector table will be in the wrong spot.
User avatar
noattack
Posts: 147
Joined: Tue Feb 13, 2007 9:02 pm
Location: Richmond, VA

Post by noattack »

Thanks, that makes a lot of sense.

So that explains why the CHR-ROM/RAM sits at the end of the source code, since it's not in that $4000 memory space allocated to the single bank of PRG. It sits on separate hardware, so it has its own memory allocation. I assume the .chr file is padded as well, since it's expected to fill 8k.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

Correct @ all of that.

The iNES file is layed out like so:

Code: Select all

$10 byte header
X * $4000 PRG-ROM banks
X * $2000 CHR-ROM banks
The example in loopy's first post works the assembler to output all the data in this format. First with the $10 byte header, then the PRG (code/data) which is padded to be exactly $4000 bytes with the .pad directive. Followed by the $2000 bytes of CHR with the .incbin line.

And yes -- your .chr file should be exactly 8K ($2000 bytes) in size.
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Post by Gil-Galad »

Is there any ASM6 NES program source files that I can look at for reference? That way I can get a better idea on how to use this assembler.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Post by GradualGames »

Personally I found it instructive to translate Michael Martin's NES101 tut from P65 to NESASM and then to ASM6 (using ASM6's readme). In retrospect, it may have been easier to go straight from P65 to ASM6. They share some features.
frantik
Posts: 370
Joined: Tue Mar 03, 2009 3:56 pm

Post by frantik »

User avatar
eleventhirtyfour
Posts: 14
Joined: Mon Mar 16, 2009 7:12 am
Location: St. Charles, MO
Contact:

Post by eleventhirtyfour »

Also, how the hell can you get ASM6 to work on Windows Vista? (or most everything else for that matter?!)

It seems like most programs that I try to run (including ASM6) do this thing where they pop up for about a half-second and then disappear.

If anyone knows what I'm talking about and has a suggestion (besides a different OS) let me know. Thanks. Sorry for slightly off-topic rant.
http://cashcrate.com/1057292 (a good place to make a little extra spending $)

http://swagbucks.com/?cmd=sb-register&rb=371826 (a fun way to win free video games and consoles)
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

eleventhirtyfour wrote:It seems like most programs that I try to run (including ASM6) do this thing where they pop up for about a half-second and then disappear.
It's a command-line program. It prints a message to the screen, probably giving hints on how to use it, but when it finishes, Windows closes its window. If you want the message to stay on the screen, you have to run it from the Windows command prompt. If you have never used MS-DOS or the Windows command prompt, try reading the "command line interface" section of this page and some of these tutorials.
Post Reply