NESASM & AOROM Programming - a few questions

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
tokumaru
Posts: 11863
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM & AOROM Programming - a few questions

Post by tokumaru » Sun Aug 20, 2017 10:30 am

One way to handle the fact that different fields have to be written to the same register is to have separate variables in RAM for each field, which in this case are the bank index and the name table, that you can change independently and call separate "WriteToMapper" function that combines the fields and writes the result to the mapper. Kinda like this:

Code: Select all

  ;select bank 4
  lda #$04
  sta BankIndex
  jsr WriteToMapper

Code: Select all

  ;select name table B
  lda #NAME_TABLE_B
  sta NameTable
  jsr WriteToMapper

Code: Select all

WriteToMapper:
  lda BankIndex
  ora NameTable
  tax
  ;lda NoBusConflicts, x ;this is only needed if the values aren't sequential
  sta NoBusConflicts, x
  rts
A setup like this allows you to control the bank and the mirroring separately.

As for the complexity of coding for AxROM, I think it all comes from the fact that you don't have a fixed bank, so you need a workaround to "fake" a fixed bank, where all the interrupt handlers and bankswitch operations must be, and depending on the assembler, faking this fixed bank might not be the most straightforward thing to do.

User avatar
tokumaru
Posts: 11863
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: NESASM & AOROM Programming - a few questions

Post by tokumaru » Sun Aug 20, 2017 11:59 am

OK, I did some tests and IFNDEF doesn't work 100% (if you reference the label before the IFNDEF block it apparently is considered defined!), so I tried a different method of creating a virtual fixed bank, using macros instead of includes and the type of label assignment I used in ASM6 (Label = *). Here's the basic template I made:

Code: Select all

	.inesprg 4
	.ineschr 0
	.inesmap 7
	.inesmir 0

;----------------------------------------------------------------
; CONSTANTS
;----------------------------------------------------------------

NAME_TABLE_A = %00000000
NAME_TABLE_B = %00001000

;----------------------------------------------------------------
; VARIABLES
;----------------------------------------------------------------

	.rsset $000

NameTable .rs 1
FrameCounter .rs 1

;----------------------------------------------------------------
; MACRO: Selects a bank without affecting name table mirroring (A = bank index)
;----------------------------------------------------------------
SelectBank .macro

	ora <NameTable
	tax
	lda BankswitchTable, x
	sta BankswitchTable, x

	.endm

;----------------------------------------------------------------
; MACRO: Selects a name table without switching banks (A = name table)
;----------------------------------------------------------------
SelectNameTable .macro

	sta <NameTable
	ora CurrentBank
	tax
	lda BankswitchTable, x
	sta BankswitchTable, x

	.endm

;----------------------------------------------------------------
; MACRO: Generates an instance of the virtual fixed bank
;----------------------------------------------------------------
GenerateFixedBank .macro

FixedBankStart\@: ;global label to isolate local labels inside

	.org $ff00 ;this address should be adjusted according to the size of this repeated chunk

CallDoSomething = *

	;remember the current bank
	lda CurrentBank
	pha

	;select the function's bank and call it
	lda #BANK(DoSomething) / 4
	SelectBank
	jsr DoSomething

	;restore the previous bank and return
	pla
	SelectBank
	rts

Reset = *

	;initialize the name table
	lda #NAME_TABLE_A
	SelectNameTable

	;switch to the bank that contains the initialization code and jump to it
	lda #BANK(Initialize) / 4
	SelectBank
	jmp Initialize

NMI = *

	;indicate that an NMI happened
	inc <FrameCounter

	;return
	rti

IRQ = *

	;return
	rti

.CurrentBank:

	;index of the current bank
	.db BANK(.CurrentBank) / 4

CurrentBank = .CurrentBank ;make this address visible from the outside

BankswitchTable = *

	;values used to avoid bus conflicts
	.db $00, $01, $02, $03, $04, $05, $06, $07, $10, $11, $12, $13, $14, $15, $16, $17

	;interrupt vectors
	.org $fffa
	.dw NMI
	.dw Reset
	.dw IRQ

FixedBankEnd\@: ;global label to isolate local labels inside

	.endm

;----------------------------------------------------------------
; PRG-ROM
;----------------------------------------------------------------

	.bank 0
	.org $8000

Initialize:

	sei
	cld

	;(INITIALIZATION CODE GOES HERE)

Forever:

	;call a function in another bank
	jsr CallDoSomething

	;select name table B
	lda #NAME_TABLE_B
	SelectNameTable

	;loop forever
	jmp Forever

	.bank 1
	.org $a000

	.bank 2
	.org $c000

	.bank 3
	.org $e000

	GenerateFixedBank

	.bank 4
	.org $8000

DoSomething:

	;(THIS IS A FUNCTION IN ANOTHER BANK)

	;return
	rts

	.bank 5
	.org $a000

	.bank 6
	.org $c000

	.bank 7
	.org $e000

	GenerateFixedBank
Note that while the mirroring can be changed from anywhere (since the bank doesn't change), changing banks can only be done from the fixed bank, so you need to create trampoline functions in the fixed bank for inter-bank calls, like the "CallDoSomething" I put in the template. You could also make a more complex, generic trampoline function that can call any address, as opposed to making individual trampolines for each function.

Another important detail about this template is that it uses the "all in main" approach to handling vblanks. The NMI simply changes a a variable, that you have to watch in the main loop in order to wait for vblank. This is a very newbie-friendly approach and I figured the template would look simpler with it, but you could also turn the NMI handler into a trampoline to a more specialized handler if you wanted to.

EDIT: Corrected a few mistakes in the code.

Post Reply