11

Take the following machine code for the 65c816 for the SNES (Super Nintendo):

00000000  ea ea 78 18 fb c2 18 a2  ff 1f 9a 20 fa 80 e2 20  |..x........ ... |
00000010  a9 80 8d 00 21 a9 e0 8d  22 21 a9 00 8d 22 21 a9  |....!..."!..."!.|
00000020  0f 8d 00 21 ea ea ea ea  ea ea ea ea ea ea ea ea  |...!............|
00000030  ea ea ea ea ea ea ea ea  ea ea ea ea ea ea ea ea  |................|
00000040  ea ea ea ea ea ea ea ea  ea ea ea ea ea ea ea ea  |................|
00000050  ea ea ea ea ea ea ea 4c  57 80 40 00 00 00 00 00  |.......LW.@.....|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 40 e2 30 a9 8f 8d 00  |[email protected]....|
00000100  21 9c 01 21 9c 02 21 9c  03 21 9c 05 21 9c 06 21  |!..!..!..!..!..!|
00000110  9c 07 21 9c 08 21 9c 09  21 9c 0a 21 9c 0b 21 9c  |..!..!..!..!..!.|
00000120  0c 21 9c 0d 21 9c 0d 21  a9 ff 8d 0e 21 8d 10 21  |.!..!..!....!..!|
00000130  8d 12 21 8d 14 21 a9 07  8d 0e 21 8d 10 21 8d 12  |..!..!....!..!..|
00000140  21 8d 14 21 9c 0f 21 9c  0f 21 9c 11 21 9c 11 21  |!..!..!..!..!..!|
00000150  9c 13 21 9c 13 21 a9 80  8d 15 21 9c 16 21 9c 17  |..!..!....!..!..|
00000160  21 9c 1a 21 9c 1b 21 a9  01 8d 1b 21 9c 1c 21 9c  |!..!..!....!..!.|
00000170  1c 21 9c 1d 21 9c 1d 21  9c 1e 21 8d 1e 21 9c 1f  |.!..!..!..!..!..|
00000180  21 9c 1f 21 9c 20 21 9c  20 21 9c 21 21 9c 23 21  |!..!. !. !.!!.#!|
00000190  9c 24 21 9c 25 21 9c 26  21 9c 27 21 9c 28 21 9c  |.$!.%!.&!.'!.(!.|
000001a0  29 21 9c 2a 21 9c 2b 21  8d 2c 21 9c 2d 21 9c 2e  |)!.*!.+!.,!.-!..|
000001b0  21 9c 2f 21 a9 30 8d 30  21 9c 31 21 a9 e0 8d 32  |!./!.0.0!.1!...2|
000001c0  21 9c 33 21 9c 00 42 a9  ff 8d 01 42 9c 02 42 9c  |!.3!..B....B..B.|
000001d0  03 42 9c 04 42 9c 05 42  9c 06 42 9c 07 42 9c 08  |.B..B..B..B..B..|
000001e0  42 9c 09 42 9c 0a 42 9c  0b 42 9c 0c 42 9c 0d 42  |B..B..B..B..B..B|
000001f0  58 60 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |X`..............|

This is almost verbatim the assembled source from https://en.wikibooks.org/wiki/Super_NES_Programming/Initialization_Tutorial The only difference is some NOP instructions inserted at various places to make it more readable to myself.

I'm trying to write an emulator and I'm working on the JSR instruction at the moment.

From http://www.6502.org/tutorials/65c816opcodes.html#6.2.2.1 this is what I have:

"JSR pushes the 16-bit address (i.e. the program counter) of the JSR instruction plus 2 onto the stack, and jumps to an address within the current program bank. In other words, the address pushed is one less than the address of the next instruction. The high byte is pushed first, then the low byte is pushed."

So this is fine. The problem is this. Look at this line:

00000000  ea ea 78 18 fb c2 18 a2  ff 1f 9a 20 fa 80 e2 20  |..x........ ... |

0x20 is the opcode for JSR. It takes a two byte argument, i.e. 0xfa and 0x80 (little endian), so it basically jumps to the absolute address 0x80fa within bank 0 and pushes the return address for the next instruction (the one after the JSR and its operand) onto the stack.

However, from looking at the source code, I know that the JSR should jump to somewhere here:

000000f0  00 00 00 00 00 00 00 00  00 40 e2 30 a9 8f 8d 00  |[email protected]....|

This can be seen from this excerpt of the source:

 .MACRO Snes_Init
    sei         ; Disabled interrupts
    clc         ; clear carry to switch to native mode
    xce         ; Xchange carry & emulation bit. native mode
    rep     #$18    ; Binary mode (decimal mode off), X/Y 16 bit
         ldx    #$1FFF  ; set stack to $1FFF
         txs

         jsr Init
 .ENDM

 .bank 0
 .section "Snes_Init" SEMIFREE
 Init:
    nop
    sep     #$30    ; X,Y,A are 8 bit numbers
    nop
    lda     #$8F    ; screen off, full brightness
    sta     $2100   ; brightness + screen enable register 
    stz     $2101   ; Sprite register (size + address in VRAM) 
    stz     $2102   ; Sprite registers (address of sprite memory [OAM])
    stz     $2103   ;    ""                       ""
    ...

From this it makes sense to me that the target address should be 0x00fa (within bank 0), not 0x80fa (within bank 0).

Address 0x80fa seems to be part of the ROM, but at this address there is nothing, only null bytes.

Why is the machine code for "jsr Init" assembled to "20 fa 80"? I know this is little endian, again my question is why the operand address doesn't make sense.

I can only assume I'm missing some fundamentals about how the 65c816 works, overlook something obvious, or that the address is relative to some other address. But the RESET vector in this particular case is 0, so I'm not getting what it should be relative to.

As a bonus question:

Why is there a 0x40 (an RTI opcode, I believe) before the first nop at the Init: label?

More information:

The assembled and linked ROM runs fine in well-known emulators, there is nothing wrong with the assembled machine code itself. It was assembled using "WLA 65816 Macro Assembler v9.8a", if it matters.

2 Answers 2

19

JSR works how you think — the program counter will head off to 80fa — but the SNES doesn't. The two most common memory mappers both mirror what's at $00xx at $80xx.

So when the processor reads from $80fa it gets the same thing as if it read from $00fa.

Per the linked article, in a 'HiROM' (i.e. one of the two common types):

Banks $80 - $FF can also be used for faster memory access. Many portions of memory <$80 are accessed at 2.68 MHz (200 ns). Accessing memory >$80 is done at 3.58 MHz (120 ns) if the value at address $420d (hardware register) is set to 1.

So the assembler is even likely to prefer the mirrored addresses for speed.

4
  • 3
    Oh lord. I should have read more documentation, especially about the memory mapping of the SNES, instead of just jumping into writing code. Thanks. Commented Jun 21, 2018 at 19:00
  • How does that explain why the assembler would see "80xx" instead of "00xx" when the program was created? Commented Jun 21, 2018 at 20:12
  • @WillHartung it more explained why a selection of 80xx rather than 00xx shouldn't affect ones understanding of JSR; I've had a longer read of the linked article and added a quote as to why it might make that selection.
    – Tommy
    Commented Jun 21, 2018 at 20:30
  • 3
    You misunderstood the quoted paragraph. "Banks $80 - $FF" means $80xxxx through $FFxxxx, not $80xx through $FFxx. The article does go on to say "LoROM basically means that the address line A15 is ignored by the cartridge, so the cartridge doesn't distinguish between $0000-$7FFF and $8000-$FFFF in any bank." But that contradicts the memory map earlier in the article, and seems inconsistent with $420d being a hardware register, so I think it's incorrect.
    – benrg
    Commented Oct 14, 2021 at 5:16
5

The header file that is used in your linked source code directs WLA to compile for a ROM using the LOROM map and the first 32K of ROM are mapped into bank 0 at offsets $8000-$FFFF. So your (mapped) code starts at offset $8000, not 0.

As for the VBlank label: the header expects an NMI routine at that label, so your code must define an interrupt routine for it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .