NESASM3 - Reset code compiled to wrong location

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
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

NESASM3 - Reset code compiled to wrong location

Post by SusiKette »

I have organized the reset code of my reset code to $E000 as it is a fixed bank in the mapper I'm using. For some reason the code is regardless put to $8000 and causes the code not to run because the reset vector still throws PC to $E000 which at the moment has nothing in it. Is there a way to fix this?

EDIT: Code might not help, but I'll include it anyway

Code: Select all

 .inesprg 1
 .ineschr 2
 .inesmap 24
 .inesmir 0

 .include "constants.asm"
 .include "variables.asm"

 .bank 2
 .org $0000

 .bank 0
 .org $E000

 .include "reset.asm"
 .include "main_loop.asm"
 .include "nmi.asm"
 .include "irq.asm"

 .bank 1
 .org $FFFA
 .dw NMI
 .dw RESET
 .dw IRQ
Avatar is pixel art of Noah Prime from Astral Chain
zzo38
Posts: 1096
Joined: Mon Feb 07, 2011 12:46 pm

Re: NESASM3 - Reset code compiled to wrong location

Post by zzo38 »

The .bank command in NESASM uses 8K banks, regardless of the mapper. Therefore, you have to specify .bank 1 if you want to address $E000 (on VRC6, that address is fixed to the last bank; you specified one 16K bank, which is two 8K banks, so the bank number for NESASM will be 1). (NESASM is confusing to many people. I like it, but be aware that it can be confusing if you do not understand it.)
(Free Hero Mesh - FOSS puzzle game engine)
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: NESASM3 - Reset code compiled to wrong location

Post by SusiKette »

Are there any good alternate assemblers where banking would be simpler to handle? Preferrably something with similar syntax (aside actual assembly code) or a well written tutorial?
Avatar is pixel art of Noah Prime from Astral Chain
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: NESASM3 - Reset code compiled to wrong location

Post by Pokun »

The two most popular assemblers in the NESDEV community are Asm6 and Ca65. The later is the more advanced assembler but it also requires more stuff to setup. Asm6 is the most popular because it's as simple as Nesasm but without the weirdness of the bank statements and whatnot. Syntax is very similar between all three assemblers (although porting a large project to another syntax takes time) so I don't think you will have any trouble. There are examples and good readme files for both Asm6 and Ca65 (unlike for NESASM).

I've used both and I prefer Asm6 in general because of it's simpleness and because I haven't really needed the advanced features of Ca65 yet.
There's a fork of Asm6, that I use, that adds some extra features like iNES header directives (which I don't use myself).

Asm6:
http://3dscapture.com/NES/

Asm6 fork (added illegal opcodes, iNES header directives etc):
https://github.com/freem/asm6f

Asm6 fork with 65816 support (if you are getting into SNES dev):
https://github.com/nicklausw/asm16

Asm6 templates (NROM, UNROM and CNROM versions):
viewtopic.php?t=6160

Nerdy night examples converted to Asm6:
https://forums.nesdev.com/viewtopic.php?f=10&t=12097

Minimal NES example using Ca65 (NROM and FDS versions):
viewtopic.php?t=11151
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: NESASM3 - Reset code compiled to wrong location

Post by koitsu »

SusiKette wrote:Are there any good alternate assemblers where banking would be simpler to handle? Preferrably something with similar syntax (aside actual assembly code) or a well written tutorial?
Short answer: there are no other assemblers that I know of that have "native" comprehension of the NES's mapper constructs (see: "banks"), nor the fact that some mappers use smaller PRG banks (ex. 8KByte) than what the NES header was originally engineered for (16KByte)).

You don't technically "need" an assembler with knowledge of such construct as long as you organise your assembly code (ex. main.asm) so that the PRG banks in the resulting ROM match expected reality. It's entirely reasonable to do something like in the olden days, where you'd have a file for the NES header, as well as each PRG bank, then assemble those files to resulting .bin or .o files, then concatenate the results a la copy /b nesheader.bin+bank00.bin+bank01.bin+bank02.bin+...+chr.bin > mygame.nes. This approach/model still works today with literally any assembler.

Likewise, you don't technically "need" an assembler that has NES header knowledge, either.

I acknowledge that all of these things might make your life easier, but as you're learning, very quickly that becomes not the case when the assembler doesn't have native comprehension of every single mapper in existence (see: varying PRG bank sizes). It's entirely subjective, but NESASM is one of those assemblers that tries to hide/simplify a lot of the inner details of mapper nuances and ROM creation -- things that the programmer/developer really needs to know about and not have hidden from them.
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: NESASM3 - Reset code compiled to wrong location

Post by Pokun »

From what I understand, the bank statements comes from a time when Nesasm was a combined PC-Engine and NES assembler (which in turn seems to be derrived from another assembler). Standard PC-Engine HuCard ROM configuration requires 8 kB bank divisions. Then it was split into Pceas and Nesasm by removing the PC-Engine support in Nesasm and vice versa, but the bank requirements remained in both assemblers.

The bank statement was finally removed in a later Pceas fork, as it really isn't that useful and only gets in the way when you are assembling something that isn't the standard HuCard. Nesasm also has some forks, but I don't think any of them totally removed the bank limitation.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM3 - Reset code compiled to wrong location

Post by tokumaru »

Well, some sort of bank management is nice, so you can automatically get bank numbers from labels... What's dumb about NESASM's implementation is that it forces banks to a fixed size (crippling the usability of the bank numbers if you use other sizes) and that it handles overflows pretty badly (IIRC, code gets overwritten with no warnings).

The BANK directive could still be useful if it just defined a bank number for the labels that follow, without affecting the structure of the output file in any way.
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: NESASM3 - Reset code compiled to wrong location

Post by SusiKette »

I took a look at asm6's readme file but it doesn't really explain banking too well.

Something that would help is to know how certain banking results to the output file. Some mappers have fixed banks as well. Can this bank be loaded to swappable bank and which bank do emulators read as the fixed bank (frist, last, etc.)
Avatar is pixel art of Noah Prime from Astral Chain
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM3 - Reset code compiled to wrong location

Post by tokumaru »

SusiKette wrote:I took a look at asm6's readme file but it doesn't really explain banking too well.
Banking in ASM6 is all up to you. The key thing you need is the BASE directive, which you use to rollback the PC for the next bank. For example, this is how you write two 8KB banks that get mapped to $8000:

Code: Select all

.base $8000 ;bank starts at $8000
  ;(bank contents here)
.org $A000 ;pad all the way to $A000 to make sure it's 8KB

.base $8000 ;roll back to $8000 for the next bank
  ;(bank contents here)
.org $A000 ;pad this to fill the whole 8KB as well
You can make this easier to manage using macros if you want. Say, a macro called StartBank could receive the start address and the bank size as parameters, and it'd calculate the final address and save it in a symbol to be used by another macro, called EndBank. This would make it easier to change the mapping address and even the sizes of the banks if you needed. For example:

Code: Select all

.macro StartBank StartAddress, BankSize
  .base StartAddress
  PaddingAddress = StartAddress + BankSize
.endm

.macro EndBank
  .org PaddingAddress
.endm

;(...)

StartBank $C000, $8000 ;16KB PRG-ROM bank at $C000
  ;(code for the fixed bank)
EndBank

StartBank $0000, $1000 ;4K CHR-ROM bank at PPU $0000
  .incbin "chr-bank-0.chr"
EndBank
Some mappers have fixed banks as well. Can this bank be loaded to swappable bank and which bank do emulators read as the fixed bank (frist, last, etc.)
Yes, the fixed bank can be loaded in another slot, but why would you ever need the same bank mapped multiple times across the addressing space? The fixed bank is normally the last, and it's normally mapped to the upper end of the addressing space (because of the interrupt vectors), but it depends on the mapper.

EDIT: Wait, I'm not sure you can save values for later in ASM6 macros, since the documentation says that "Labels defined inside macros are local"... That's something that needs testing.
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: NESASM3 - Reset code compiled to wrong location

Post by SusiKette »

tokumaru wrote: Banking in ASM6 is all up to you. The key thing you need is the BASE directive, which you use to rollback the PC for the next bank. For example, this is how you write two 8KB banks that get mapped to $8000:

Code: Select all

.base $8000 ;bank starts at $8000
  ;(bank contents here)
.org $A000 ;pad all the way to $A000 to make sure it's 8KB

.base $8000 ;roll back to $8000 for the next bank
  ;(bank contents here)
.org $A000 ;pad this to fill the whole 8KB as well
Can banks mapped to $8000 be only loaded there? Since the ROM stores the banks in 16k chuncks would this create two 16k banks with the upper 8k blank or combine them to one 16k bank.

If I use a mapper with swappable 16k bank at $8000 - $BFFF, swappable 8k bank at $C000 - $DFFF, can the 8k bank can select either the upper or lower half of a 16k bank? Selecting the correct 8k bank probably is number of 16k bank * 2 (+1 if upper half). At least VRC6 seems to have 5 bits on the 8k bank select register while 16k has 4 bits.
Avatar is pixel art of Noah Prime from Astral Chain
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: NESASM3 - Reset code compiled to wrong location

Post by koitsu »

It's pretty simple: "simple" assemblers like asm6 do not know anything about "banks" (PRG, CHR, anything else), nor does it know anything about mappers.

All you, as the programmer, need to know is the NES file format to understand how to organise your resulting .nes file, and keep track (yourself) of PRG bank numbers yourself.

First, the file format: the Wiki describes the file format in a way that I've maintained doesn't do a good job visualising the file itself, which makes it hard for people to understand the format, but it's there: https://wiki.nesdev.com/w/index.php/INE ... ile_format

I do not care about the NES 2.0 header so I'm not going to describe it -- all it will do is confuse you right now.

The 16-byte NES header comes first, followed by the first 16KB PRG bank, followed by the next 16KB PRG bank, etc., followed by the first 8KB CHR-ROM bank, followed by the next 8KB CHR bank, etc.. until the end of the file. The number of PRG and CHR-ROM banks should be base-2-compliant, e.g. 0, 1, 2, 4, 8, etc.. not values like 3 or 7. If you're using CHR-RAM, you use a value of 0 for CHR bank count.

The file format header always assumes 16KByte PRG banks and 8KByte CHR-ROM banks. Always. It doesn't matter what mapper number you put in the header.

So as an example: if you were making an MMC1 game (the mapper which uses 16KByte PRG banks itself), which consisted of 64KBytes total PRG-ROM (4 banks), and 64KBytes total CHR-ROM (8 banks), the header and file would look like this:

Code: Select all

NES mapper: Nintendo MMC1 = 1 = %00000001

NES header:

Bytes 0-3: NES ROM identifier string = $4E $45 $53 $1A
Byte 4: PRG-ROM bank count = 4   (4*16KB banks = 64KB)
Byte 5: CHR-ROM bank count = 8   (8*8KB banks = 64KB)
Byte 6 bits 7-4: lower nibble of NES mapper number = %0001
Byte 6 bits 3-0: depends on what you want; see Wiki
Byte 7 bits 7-4: upper nibble of NES mapper number = %0000
Byte 7 bits 3-0: depends on what you want; see Wiki
Bytes 8-15 = 0   (refer to the Wiki if you want to know what they do)

Resulting NES ROM file itself:

First 16 bytes: NES header
Next 16384 bytes: PRG bank #0
Next 16384 bytes: PRG bank #1
Next 16384 bytes: PRG bank #2
Next 16384 bytes: PRG bank #3 -- this is also your fixed bank at $C000-FFFF
Next 8192 bytes: CHR bank #0
Next 8192 bytes: CHR bank #1
Next 8192 bytes: CHR bank #2
Next 8192 bytes: CHR bank #3
Next 8192 bytes: CHR bank #4
Next 8192 bytes: CHR bank #5
Next 8192 bytes: CHR bank #6
Next 8192 bytes: CHR bank #7
This should clarify how and why copy /b header.bin+prg00.bin+prg01.bin+...+chr00.bin+chr01.bin+... game.nes can be used to create a NES ROM.

If you're using a mapper that uses 8KByte PRG banks, the file format remains the same as I described -- you simply have to multiply by total number of 8KB PRG banks by 2 for the NES header.

For example, let's say you were using mapper 24 (VRC6a) which consisted of 64KBytes total PRG-ROM (8 banks of 8KBytes) and CHR-RAM (i.e. zero CHR-ROM), you'd have this:

Code: Select all

NES mapper: Konami VRC6a = 24 = %00011000

NES header:

Bytes 0-3: NES ROM identifier string = $4E $45 $53 $1A
Byte 4: PRG-ROM bank count = 4   (8*8KB banks / 2 due to NES header counting PRG-ROM in 16KB banks)
Byte 5: CHR-ROM bank count = 0   (using CHR-RAM)
Byte 6 bits 7-4: lower nibble of NES mapper number = %1000
Byte 6 bits 3-0: depends on what you want; see Wiki
Byte 7 bits 7-4: upper nibble of NES mapper number = %0001
Byte 7 bits 3-0: depends on what you want; see Wiki
Bytes 8-15 = 0   (refer to the Wiki if you want to know what they do)

Resulting NES ROM file itself:

First 16 bytes: NES header
Next 8192 bytes: PRG bank #0
Next 8192 bytes: PRG bank #1
Next 8192 bytes: PRG bank #2
Next 8192 bytes: PRG bank #3
Next 8192 bytes: PRG bank #4
Next 8192 bytes: PRG bank #5
Next 8192 bytes: PRG bank #6
Next 8192 bytes: PRG bank #7 -- this is also your fixed bank at $E000-FFFF
The first thing you're going to say is "wait, is the above true? VRC6a/b support having 16KB PRG-ROM at $8000-BFFF! How does that fit into this?"

Short answer: with regards to the file format, it doesn't. Whether the mapper "swaps in/out" two 8KB PRG-ROM pages (for 16KB at $8000-BFFF) or a single 8KB PRG-ROM page (for $C000-DFFF) doesn't matter to the NES file format. It matters more to the mapper. For example, say with VRC6a you told it to swap in 16KB PRG bank #1 at $8000-BFFF. What's going to end up in $8000-9FFF is 8KB PRG bank #1, and $A000-BFFF will hold 8KB PRG bank #2.

Now you understand the file format.

As for how to get all of this to work in asm6: it's not that hard, but the important piece of the puzzle is the .bank directive. Please refer to the documentation to understand what this does; it's sort of like .org (meaning it sets what the assembler considers the PC (program counter)), except unlike .org you can re-use addresses with it. It's designed for this purpose.

There's already a thread on how to use asm6 with banked mappers: viewtopic.php?t=6027

I'm in the process of writing up an example VRC6a template which you could use with asm6, except I seem to be having problems with doing a 16KB PRG bank swap using $8000 (for swapping $8000-BFFF) -- Mesen is picking a completely different bank number than what my code has, so it's likely some misunderstanding on my part, a documentation mistake, or something. Everything else works as expected.

Edit: simple mistake on my part: for 16KB swaps, you need to divide the bank number by 2, since internally the VRC6 considers all PRG banks 8KB in size. This means for 16KB bank selection, you need to ensure the bank number is evenly divisible by 2 (i.e. 0, 2, 4, etc.).

Here's the code: https://pastebin.com/Zqd2STsi

Hilariously, the captcha I got on pastebin when submitting that was DA65. *chuckle*

The code sits in an infinite loop, just running some lda/ldx/ldy statements over and over. The important part is to understand that after the VRC6a init routine runs, you end up with the following:

$8000-9FFF = PRG bank 4
$A000-BFFF = PRG bank 5
$C000-DFFF = PRG bank 3
$E000-FFFF = PRG bank 7 (fixed)

How do I know this for certain? Because, conveniently, Mesen actually visually shows you what bank's mapped where (both PRG and CHR). :-)

I may have gotten some part of the VRC6 PPU mirroring setup wrong, but that really isn't what the code was intended to demonstrate here. While I sort of understand what the Wiki documents, it does take some focus to comprehend.

Hopefully this is a bit more complete, and a bit easier to understand than tokumaru's example. I don't tend to "teach" things like this using macros, as I feel all it does is confuse people trying to understand something already complex enough.

As for "getting bank numbers" in your code, rather than hard-coding them: lots of ways to do this. I will bow out of such a conversation because it's entirely subjective. Do it in whatever way you want that makes the most sense to you. I tend to use equates or hard-code things simply because I don't like complicating matters ("cleaning things up" to be more fancy can be done at the end of whatever project I'm working on).
Last edited by koitsu on Wed Jan 16, 2019 4:15 am, edited 1 time in total.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM3 - Reset code compiled to wrong location

Post by tokumaru »

SusiKette wrote:Can banks mapped to $8000 be only loaded there?
No, but if it contains code, that code can only be run if the bank is loaded into the intended slot, because programs contain references to absolute addresses.
Since the ROM stores the banks in 16k chuncks would this create two 16k banks with the upper 8k blank
No. The fact that iNES ROM sizes are specified in 16KB chunks was just an unfortunate decision by the person who created the format. It was an arbitrary choice and we only use that to specify how large the entire PRG-ROM is in multiples of 16KB, but that has absolutely nothing to do with how big the actual banks are. The mapper dictates how big the banks are.
combine them to one 16k bank.
My example would output 16KB, but that shouldn't be called a bank, for the reasons I explained above. It's a total of 16KB, because it contains 2 8KB banks.
If I use a mapper with swappable 16k bank at $8000 - $BFFF, swappable 8k bank at $C000 - $DFFF, can the 8k bank can select either the upper or lower half of a 16k bank?
I can't think of any mapper with mixed PRG bank sizes like that, but according to the logic used in some mappers that have mixed CHR bank sizes, yes that would be possible. However, when mapping 8KB banks in the 16KB slot, it'd always be two consecutive banks, even bank first.
Selecting the correct 8k bank probably is number of 16k bank * 2 (+1 if upper half).
In the mappers I'm familiar with, bank indices always reference the smaller bank size, and the lower bit is ignored when mapping to a larger slot (since it's always even + odd, like I said above).
At least VRC6 seems to have 5 bits on the 8k bank select register while 16k has 4 bits.
I'm not really familiar with the VRC6.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: NESASM3 - Reset code compiled to wrong location

Post by koitsu »

tokumaru wrote:The fact that iNES ROM sizes are specified in 16KB chunks was just an unfortunate decision by the person who created the format. It was an arbitrary choice and ...
Just some pedantry on my part (AND THIS SHOULD NOT DE-RAIL THE THREAD PLEASE): 16KB was not an arbitrary choice. The word "arbitrary" means "random or without reason".

16KB was chosen because at the time the .nes file format was created, neither Marat Fayzullin (a.k.a. RST38h; inventor) nor Alex Krasivsky (a.k.a. Landy) had come across any commercial games using mappers that used any sizes smaller than 16KB. It was an understandable choice given what hardware for reverse-engineering was available to them at the time, which was sometime in 1995. Marat could not predict that there were a bunch of other games in Japan (and clones in China/Taiwan/Hong Kong) that had all sorts of other mappers (there was literally no cross-communication between Russian, Japanese, American, European, etc. emulator authors/fans, partially due to the state of the Internet then, and partially due to language barriers).

VRC6 wasn't "discovered" until a Japanese fellow by the name of Goroh took the first efforts to reverse-engineer a particular VRC6 game (I forget which one) for the benefit of emulation. This was in 1997 or 1998 sometime (I suspect very late 1997 / early 1998, but my memory is hazy). Firebug also happened to be doing similar mapper reverse-engineering efforts around the same time, and into the later 90s with his docs (there were many revisions). Kevin Horton/kevtris then did his own in mid-1999.

You probably don't know that the .nes file format *wasn't* the first Famicom/NES-oriented file format. What we had prior to that were a result of the Japanese Pasofami emulator -- circa late 1992/early 1993 -- for the FM Towns, and later Windows (by Nobuaki ANDOU). This file format consisted of 3 separate files: .prg, .chr, and .prm; the latter file is what defined details about the game itself, akin to the 16-byte NES header in functionality, but very different. It was a 34-byte file in ASCII, where single letters or pairs of letters/numbers represented settings, but had no PRG/CHR sizes or counts in it, though it does have knowledge of some Namco mappers that used 8KB PRG. If you want to see the format, it's described in Japanese: here you go.

Nathan Altice's book titled "I AM ERROR" goes over some of this file format history. If you'd like a hard copy I'd be happy to buy it for you + send it to Brazil. There's probably a digital version online somewhere though, but IMO it's still worth buying.

In short: it wasn't an arbitrary choice. Whether or not it was a "unfortunate" decision is entirely subjective -- it helps to know the "why" and the history. That said, the only thing I find unfortunate about the .nes file format is that more space wasn't given to the header (e.g. 32 or 64 bytes), as it'd have made extensions/expansion a lot easier. But we can't rewind time, so we move forward. :-)

Edit: tepples, if you want to split this post/subject off into a separate thread, I'm all for it.
Pokun
Posts: 2681
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: NESASM3 - Reset code compiled to wrong location

Post by Pokun »

tokumaru wrote:Well, some sort of bank management is nice, so you can automatically get bank numbers from labels... What's dumb about NESASM's implementation is that it forces banks to a fixed size (crippling the usability of the bank numbers if you use other sizes) and that it handles overflows pretty badly (IIRC, code gets overwritten with no warnings).

The BANK directive could still be useful if it just defined a bank number for the labels that follow, without affecting the structure of the output file in any way.
Yeah well the Pceas fork just removed that dumb limitation. It still allows handling banks, with the bank directive and the BANK() function, and which is necessary for HuCards.


Asm6 can handle banks for most mappers using .base and .org directives, but I'm not sure how to deal with FDS files. The way I did it was to assemble every file separately using a batch script then finally assemble the header file which .incbins the other already assembled files so it becomes one FDS image. It works but it makes handling variables globally a hassle, as they aren't shared by the separately assembled files. Haven't touched the FDS in a long while though.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM3 - Reset code compiled to wrong location

Post by tokumaru »

I don't know much about the FDS so I don't know what assembling programs for it harder, but I bet there's a way to make it work in a single assembly.

I do miss some sort of bank management in ASM6 though... For a long while I used a global symbol to hold the current bank number and for every label whose bank I would need to reference I'd define an extra symbol like this: LabelName_Bank = CurrentBank It worked, but having to define all these extra symbols was kind of annoying... I'd much rather have the assembler automatically assign bank numbers to all labels.
Post Reply