SNES dev newbie questions

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
SNES dev newbie questions
by on (#82917)
Hey all,

I've just started fiddling around with SNES stuff, more specifically with the Wikibooks tutorial. I have some (probably very) basic questions for which I haven't found an answer on these boards (or byuu's); if I am supposed to find the answers by more research or by moving forward, please don't hesitate to me so. Here's a basic change the BG color example I will refer to in my questions:

Code:
.include "Header.inc"
.include "Snes_Init.asm"

VBlank:
  RTI

.bank 0
.section "MainCode"

Start:
    Snes_Init

    ; Set the background color to green.
    sep     #$20        ; Set the A register to 8-bit.
    lda     #%10000000  ; Force VBlank by turning off the screen.
    sta     $2100
    lda     #%11100000  ; Load the low byte of the green color.
    sta     $2122
    lda     #%00000000  ; Load the high byte of the green color.
    sta     $2122
    lda     #%00001111  ; End VBlank, setting brightness to 15 (100%).
    sta     $2100

Forever:
    jmp Forever

.ends
  1. Is there a way to load both bytes into $2122 at once? It seems kinda strange to me to set the register to 8 bits, load 8 bits, then load 8 more bits, rather than doing all of this with just one lda/sta.
  2. Speaking of which... How come doing sta $2122 twice with a byte both times doesn't end up just writing the latest byte of the two? Is writing the low byte first, and the the high byte the "standard way" of setting a color value here?
  3. I tried copying and pasting the "Set the background [...]" block so that there's two of them, changing the color to red the second time. By moving the Forever label at the top of the first of these blocks, I expected alternating red and green as the background color, but I end up with a rather trippy effect involving red and black only. Am I supposed to do something between (or during) VBlanks?
Thanks a bunch for reading, and big thanks to all the passionnate and helpful people around here - I admire all you've done and are doing for the community and beyond! :)
Re: SNES dev newbie questions
by on (#82955)
juef wrote:
Code:
    ; Set the background color to green.
    sep     #$20        ; Set the A register to 8-bit.
    lda     #%10000000  ; Force VBlank by turning off the screen.
    sta     $2100
    lda     #%11100000  ; Load the low byte of the green color.
    sta     $2122
    lda     #%00000000  ; Load the high byte of the green color.
    sta     $2122
    lda     #%00001111  ; End VBlank, setting brightness to 15 (100%).
    sta     $2100

Is there a way to load both bytes into $2122 at once? It seems kinda strange to me to set the register to 8 bits, load 8 bits, then load 8 more bits, rather than doing all of this with just one lda/sta.


No. $2122 is an 8-bit memory-mapped register for colour data, the data of which is 16-bits; each write to it is handled internally by the PPU unit on the system. There's no "direct" 16-bit equivalent. If you were to write a 16-bit value to $2122, the LSB would end up going into $2122, and the MSB would end up going into $2123 which is a completely different register (window mask setting).

Per my old SNES documentation:

Code:
| w   |$2121  |Colour # (or pallete) selection register [CGADD]              |
|     |       |xxxxxxxx              x: Address (color #).                   |
|     |       |                                                              |
|     |       |                                                              |
| wd  |$2122  |Colour data register [CGDATA]                                 |
|     |       |xxxxxxxx              x: Value of colour.                     |
|     |       |                                                              |
|     |       |SNES colour is 15 bit; 5 bits for red, green, and blue. The   |
|     |       |order isn't RGB though: It's BGR (RGB reversed!).             |


I should note you aren't setting $2121 to the index/offset of the palette entry you wish to modify prior to doing it -- so I assume your init_snes routine is what's responsible. I have to assume you're modifying colour #0. Just an FYI.

juef wrote:
Speaking of which... How come doing sta $2122 twice with a byte both times doesn't end up just writing the latest byte of the two? Is writing the low byte first, and the the high byte the "standard way" of setting a color value here?


Because it's an 8-bit memory-mapped register. Each write is handled by the PPU. You should probably find a copy of my old SNES documentation and review the SNES.1 file. Both bytes written via the STA statements are honoured by the PPU/used by VRAM.

juef wrote:
I tried copying and pasting the "Set the background [...]" block so that there's two of them, changing the color to red the second time. By moving the Forever label at the top of the first of these blocks, I expected alternating red and green as the background color, but I end up with a rather trippy effect involving red and black only. Am I supposed to do something between (or during) VBlanks?


What's happening is that you're updating the screen "too fast" and not seeing what's going on (visually). This is further complicated by your desire to turn on/off VBlank.

You have two options, and I recommend the former:

1) Leave the screen on at all times/set brightness to 100% and do your screen updates (writes to $2122) inside of VBlank time (NMI). You can wait for the NMI by examining bit 7 of $4210. You'll need to tie VBlank to NMI by setting bit 7 of $4200 as well. You can use the WAI opcode to wait for NMI rather than have to poll $4210.

2) Leave the screen on at all times/set brightness to 100% and do everything in your main loop but instead of disabling VBlank, you need to wait for it. See bit 7 of $4212.

In either case you need to add some delays. The screen is updated at 60 times a second (50 if PAL). The easiest way to delay is to wait for VBlank repetitively (more waits = more delay). I used to use something like this:

Code:
        rep #$10
        ldx #200        ; Higher value = longer delay
delay   jsr WaitVBL
        dex
        bpl delay
        sep #$10
...

;
; Note: Should make sure accum is 8-bit here, not 16-bit
;
WaitVBL:
        pha
xxx     lda $4210
        bpl xxx
        lda $4210       ; Reset it
        pla
        rts



It might be worthwhile, as I said earlier, to download my old SNESdoc. Inside of that archive is an another archive called TEST.LZH, which contains some source code to a very small demo used to show off some of the features on the SNES. The code is simple and should get you started, and it reads fairly easily.
Re: SNES dev newbie questions
by on (#82956)
koitsu wrote:
It might be worthwhile, as I said earlier, to download my old SNESdoc. Inside of that archive is an another archive called TEST.LZH, which contains some source code to a very small demo used to show off some of the features on the SNES. The code is simple and should get you started, and it reads fairly easily.

I have found your docs; they seem rather helpful and complete. Thanks for writing them, I definitely have to read a bit more before continuing. But most of all, thank you very much for your reply: it is much more complete and helpful than what I hoped!

by on (#83027)
As much as koitsu/yoshi deserves credit for his snes documentation which were released /long/ ago you should now refer to more complete documentation most of which can be found at http://romhacking.net or possibly http://wiki.superfamicom.org/ First and foremost you want to grab http://www.romhacking.net/docs/196/ which is a detailed description of all registers including additional info. Looking around on romhacking you will also find an (most certainly illegal) copy of the complete snes development manuals as issued by nintendo. I do have a printed copy of those but tbh the txt file mentioned before is much more useful.

Hope that helps. Enjoy!

by on (#83106)
This is probably more of a general assembly question, but I can't figure this one out. I tried to isolate the issue, which is why this code is missing a lot of important stuff:
Code:
.include "header.inc"
.include "Snes_Init.asm"
VBlank:
    RTI
.bank 0
.section "MainCode"
Start:
   Snes_Init
   sep     #$20
   
;TestTable:
;   .db $00

Forever:
   lda   #%00011111
   sta   $2122
   lda   #%00000000
   sta   $2122
   lda   #%00001111
   sta   $2100

    jmp Forever
.ends

As it is above, the code colors the screen red. What confuses me is that uncommenting the two TestTable lines makes the screen green instead. I don't refer to this table anywhere, so I guess somehow adding this piece of code messes with the registers?

Also, thanks for the help gilligan, I've checked those links out a little. Very helpful indeed!

by on (#83107)
juef wrote:
What confuses me is that uncommenting the two TestTable lines makes the screen green instead. I don't refer to this table anywhere, so I guess somehow adding this piece of code messes with the registers?

That is because it executes the data as code since you placed it in the middle of code. Move it to a place which execution will never reach, such as below the "jmp Forever" instruction.

by on (#83397)
Thank you for the reply, thefox! There's a lot of stuff I clearly haven't understood correctly yet, and that's one less of them now.

As an effort to understand better what I'm doing exactly, I've been trying to convert my source code to something that byuu's bass can compile. It's been easier than I thought, except for just this part:

Code:
.SNESHEADER
  ; [...] some useless header stuff happens here (it can be commented without problem)
  LOROM
  ; [...] some useless header stuff happens here (it can be commented without problem)
.ENDSNES

.SNESNATIVEVECTOR               ; Define Native Mode interrupt vector table
  COP EmptyHandler
  BRK EmptyHandler
  ABORT EmptyHandler
  NMI VBlank
  IRQ EmptyHandler
.ENDNATIVEVECTOR

.SNESEMUVECTOR                  ; Define Emulation Mode interrupt vector table
  COP EmptyHandler
  ABORT EmptyHandler
  NMI EmptyHandler
  RESET Start                   ; where execution starts
  IRQBRK EmptyHandler
.ENDEMUVECTOR

This is part of a 'header.inc' file that my tutorial uses. When assembled, it writes some code at the end, without which the program cannot run. For now, just copying the assembled code from WLA into the file bass assembles works just fine. I'm not too sure what it that code is/does, but mostly, I'd be interested in knowing how to translate it to bass. I'm guessing a different syntax will provide me another way of understanding it, as has happened with plenty of other stuff in the conversion so far.

Thank you all again very much!

by on (#83399)
juef wrote:
Thank you for the reply, thefox! There's a lot of stuff I clearly haven't understood correctly yet, and that's one less of them now.

As an effort to understand better what I'm doing exactly, I've been trying to convert my source code to something that byuu's bass can compile. It's been easier than I thought, except for just this part:


Refer to MukundaJay's SNES Devkit '09, for CA65.

http://snes.mukunda.com/

Header examples can be found there, More portable than WLA-DX's

or at least steal the header from your favorite ROM (at least FFC0-FFFF) such as Super Mario World (That will be terrible, though).

by on (#83402)
That isn't code at all, but it just a list of address to your interrupt routines. I guess this was considered significant enough to have it's own Wikipedia article: http://en.wikipedia.org/wiki/Interrupts_in_65xx_processors

Normally you would use .word Emptyhandler (or in CA65 .addr EmptyHandler is the same thing). You can place this stuff manually with the .org command, but it seems like assemblers usually provide some kind of way of defining it.

by on (#83409)
I don't provide additional markup, as it's really not necessary.

Code:
lorom


At the top of the file is all you need for LoROM addressing.

As for the vectors, write them directly:

Code:
org $ffe4; dw COP
org $ffe6; dw BRK
org $ffea; dw NMI
org $ffee; dw IRQ
org $fffc; dw Reset


Those are just labels to code that appears between $008000 and $00ffff. So Reset would be Start in your case. You don't HAVE to define ones you don't ever use.

You'll never use the emulation-mode ones.

by on (#83450)
Thanks you all for your answers! Now I can compile what I had done so far with bass, with which I'll probably be stickin' from now on!

by on (#83828)
I have been asked to share my code that compiles with bass. I have progressed beyond what I'm posting below, but as there's still some stuff I can't figure out and I won't have the time to look at it for a few days, I'll just share what I had successfully compiled in my previous post. All that happens is that the screen gets colored red.

File "test.asm"
Code:
incsrc "SnesInit.asm"

Start:
   {SnesInit}
   
   lda   #%00011111
   sta   $2122
   lda   #%00000000
   sta   $2122
   lda   #%00001111
   sta   $2100

Forever:
   jmp Forever


File "SnesInit.asm"
Code:
mapper lorom
org $8000; fill $010000
org $ffea; dw EmptyHandler   // SNES vectors
org $fffc; dw Start
org $8000;

macro SnesInit;
        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;
endmacro

Init:
        sep     #$30    // X,Y,A are 8 bit numbers
        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   //    ""                       ""
        stz     $2105   // Mode 0, = Graphic mode register
        stz     $2106   // noplanes, no mosaic, = Mosaic register
        stz     $2107   // Plane 0 map VRAM location
        stz     $2108   // Plane 1 map VRAM location
        stz     $2109   // Plane 2 map VRAM location
        stz     $210A   // Plane 3 map VRAM location
        stz     $210B   // Plane 0+1 Tile data location
        stz     $210C   // Plane 2+3 Tile data location
        stz     $210D   // Plane 0 scroll x (first 8 bits)
        stz     $210D   // Plane 0 scroll x (last 3 bits) #$0 - #$07ff
        lda     #$FF    // The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that.
        sta     $210E   // Plane 0 scroll y (first 8 bits)
        sta     $2110   // Plane 1 scroll y (first 8 bits)
        sta     $2112   // Plane 2 scroll y (first 8 bits)
        sta     $2114   // Plane 3 scroll y (first 8 bits)
        lda     #$07    // Since this could get quite annoying, it's better to edit the scrolling registers to fix this.
        sta     $210E   // Plane 0 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2110   // Plane 1 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2112   // Plane 2 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2114   // Plane 3 scroll y (last 3 bits) #$0 - #$07ff
        stz     $210F   // Plane 1 scroll x (first 8 bits)
        stz     $210F   // Plane 1 scroll x (last 3 bits) #$0 - #$07ff
        stz     $2111   // Plane 2 scroll x (first 8 bits)
        stz     $2111   // Plane 2 scroll x (last 3 bits) #$0 - #$07ff
        stz     $2113   // Plane 3 scroll x (first 8 bits)
        stz     $2113   // Plane 3 scroll x (last 3 bits) #$0 - #$07ff
        lda     #$80    // increase VRAM address after writing to $2119
        sta     $2115   // VRAM address increment register
        stz     $2116   // VRAM address low
        stz     $2117   // VRAM address high
        stz     $211A   // Initial Mode 7 setting register
        stz     $211B   // Mode 7 matrix parameter A register (low)
        lda     #$01
        sta     $211B   // Mode 7 matrix parameter A register (high)
        stz     $211C   // Mode 7 matrix parameter B register (low)
        stz     $211C   // Mode 7 matrix parameter B register (high)
        stz     $211D   // Mode 7 matrix parameter C register (low)
        stz     $211D   // Mode 7 matrix parameter C register (high)
        stz     $211E   // Mode 7 matrix parameter D register (low)
        sta     $211E   // Mode 7 matrix parameter D register (high)
        stz     $211F   // Mode 7 center position X register (low)
        stz     $211F   // Mode 7 center position X register (high)
        stz     $2120   // Mode 7 center position Y register (low)
        stz     $2120   // Mode 7 center position Y register (high)
        stz     $2121   // Color number register ($0-ff)
        stz     $2123   // BG1 & BG2 Window mask setting register
        stz     $2124   // BG3 & BG4 Window mask setting register
        stz     $2125   // OBJ & Color Window mask setting register
        stz     $2126   // Window 1 left position register
        stz     $2127   // Window 2 left position register
        stz     $2128   // Window 3 left position register
        stz     $2129   // Window 4 left position register
        stz     $212A   // BG1, BG2, BG3, BG4 Window Logic register
        stz     $212B   // OBJ, Color Window Logic Register (or,and,xor,xnor)
        sta     $212C   // Main Screen designation (planes, sprites enable)
        stz     $212D   // Sub Screen designation
        stz     $212E   // Window mask for Main Screen
        stz     $212F   // Window mask for Sub Screen
        lda     #$30
        sta     $2130   // Color addition & screen addition init setting
        stz     $2131   // Add/Sub sub designation for screen, sprite, color
        lda     #$E0
        sta     $2132   // color data for addition/subtraction
        stz     $2133   // Screen setting (interlace x,y/enable SFX data)
        stz     $4200   // Enable V-blank, interrupt, Joypad register
        lda     #$FF
        sta     $4201   // Programmable I/O port
        stz     $4202   // Multiplicand A
        stz     $4203   // Multiplier B
        stz     $4204   // Multiplier C
        stz     $4205   // Multiplicand C
        stz     $4206   // Divisor B
        stz     $4207   // Horizontal Count Timer
        stz     $4208   // Horizontal Count Timer MSB (most significant bit)
        stz     $4209   // Vertical Count Timer
        stz     $420A   // Vertical Count Timer MSB
        stz     $420B   // General DMA enable (bits 0-7)
        stz     $420C   // Horizontal DMA (HDMA) enable (bits 0-7)
        stz     $420D   // Access cycle designation (slow/fast rom)
        cli             // Enable interrupts
        rts

EmptyHandler:
      rti
Re: SNES dev newbie questions
by on (#126212)
Sorry to bring back such an old topic; I know it's been a long time but I've felt the desire to go back to these things recently and the above posts are still relevant to my following questions.

1) In the SNES init routine above, there is this one line:
Code:
stz     $4200   // Enable V-blank, interrupt, Joypad register

Now, from what I understand, this sets $4200 to zero, which... disables VBlank NMI, right? Shouldn't this be the following instead?

Code:
lda #$80
sta $4200

2) Following koitsu's above advice, I tried inplementing delays between my background color change. The main code goes like this: init the SNES, set the background green, wait, set it red, dim the screen a little, and then do nothing forever. The screen is indeed dimmed, but the color is still red. Any clue to why I can't get some red?

Code:
Snes_Init

sep   #$20
lda   #%10000000
sta   $2100
lda   #%11100000
sta   $2122
lda   #%00000011
sta   $2122
lda   #%00001111
sta   $2100
   
rep #$10
ldx #200
delay:
   jsr WaitVBL
   dex
   bpl delay
sep #$10

sep   #$20
lda   #%10000000
sta   $2100
lda   #%00011111
sta   $2122
lda   #%00000000
sta   $2122
lda   #%00000100
sta   $2100

Forever:
   jmp Forever

WaitVBL:
   sep   #$20
   pha
   wai
   pla
   rts

(Note that I understand the advice about keeping the screen on, but I tried this as a test when I couldn't get red.)

This is with WLADX, as I was unable to make my code suitable for the latest bass version.
Any help would be much appreciated! Thank you!
Re: SNES dev newbie questions
by on (#126216)
I don't see you setting $2121 anywhere, which means you're just blindly appending onto the end of the palette rather than changing existing on-screen colours. You probably want an stz $2121 before tinkering with $2122.

As for the // Enable V-blank, interrupt, Joypad register comment: the comment itself is wrong and you are correct -- stz $4200 would DISABLE NMI when VBlank fires, not enable. You really should turn on NMI when VBlank fires, otherwise your wai in WaitVBL doesn't serve a purpose.

Also, WaitVBL doesn't look quite right (you could replace that whole thing with just wai ; rts and be done with it; I don't see the point behind the stack operations or the sep #$20). The way the routine "waits for VBlank", BTW, is by waiting for NMI to fire using wai, which you want tied to VBlank, hence what I said in my above paragraph. :-)

You could also replace Forever: jmp Forever with just stp (STop Processor) if you wanted.
Re: SNES dev newbie questions
by on (#126227)
Upload your project somewhere in a rar file, and I can make it into bass v14 code for you, and put some comments in there of what to think of when using bass. I'm no expert, but I got some stuff working with it.
Re: SNES dev newbie questions
by on (#126229)
koitsu: thank you so much for your reply. I do have stz $2121 in Snes_Init but didn't bother recopying the entire contents of the routine as it's mostly the same as in this post, which is a file used by some tutorials I've found...

Oh, I see what you mean for the WaitVBL; I forgot to remove those extra instructions from your above reply in which you polled $4210 rather than wai. As for the stp, thank you, that's interesting. I will probably stick to the loop however as I intend to have a main, endless loop in there eventually.

Still, after making those changes and rechecking everything, I still can't seem to change the color. Any clue on why that wouln't work?

DoNotWant: that would be very kind of you! Here you go.
Re: SNES dev newbie questions
by on (#126232)
Disclaimer: I too am a Super NES noob although with NES experience.

koitsu wrote:
You really should turn on NMI when VBlank fires, otherwise your wai in WaitVBL doesn't serve a purpose.

True. But isn't it a good idea to disable NMI during the init code and reenable it afterward to make sure the NMI handler doesn't mess things up?

Quote:
you could replace that whole thing with just wai ; rts and be done with it

Possibly, although with other things turned on, it might just end up waiting for the scanline number to equal VTIMEL ($4209).
Re: SNES dev newbie questions
by on (#126238)
I've found that leaving register $4200 at zero will work on really lenient emulators like ZSNES, but won't work for BSNES, SNES9x or on the real hardware, had to learn that the HARD way...heheh...sorry. If you play with the interrupt tables and the routines they call, I think you'll have a more intuitive understanding of how they work.

It is also worthwhile to look at the actual SNES Developer manual:
http://www.romhacking.net/documents/226/

It was useful for me trying to do some HSYNC effects that required specific settings, which the SNES DEV Manual covers that were helpful to know. Here is where I got in case you are curious:

https://vimeo.com/85569649

Experiencing some hardware-specific graphic anomalies and I still haven't gotten the counters to work with any specificity (ie setting specific H or V Counter variables), but this seems to be working consistently enough for me.

With regard to changing the color, I believe you do need $2121 before you specify the color. Think about it this way, the SNES needs to know the index of the color that you want to change the value of. Only the zeroth color is the "bg" color unless you create tiles and specify color indices within them. If you were setting the palette you would need to know index number then color. You'd need to stz $2121 each time you try to change the color.

It is for WLA, but check out bazz's code:
http://wiki.superfamicom.org/snes/show/ ... ES+Program

I've done something similar trying to get a "rainbow effect" based on bazz's code. I had to specify stz $2121 each time:

http://instagram.com/p/iXay-ZP9T0

I am at work right now, but I can upload this later if it helps.
Re: SNES dev newbie questions
by on (#126239)
tepples wrote:
True. But isn't it a good idea to disable NMI during the init code and reenable it afterward to make sure the NMI handler doesn't mess things up?


I'm not sure all the specifics of how the NMI handler works, but I think it would technically be "okay" if the screen is off and you know you aren't calling any interrupts.

If it was messing things up, I'm sure you'd start to see it in a larger program (ie game)
Re: SNES dev newbie questions
by on (#126247)
First time using dropbox, so I hope I did this right.
https://www.dropbox.com/s/o7hqzji3d0kl1vz/BASS.rar

Basically, I changed ; to // for comments, changed the header, added a macro, and removed
some stuff. The macro takes a CPU address, which it turns into a ROM address.
Re: SNES dev newbie questions
by on (#126249)
tepples wrote:
koitsu wrote:
You really should turn on NMI when VBlank fires, otherwise your wai in WaitVBL doesn't serve a purpose.

True. But isn't it a good idea to disable NMI during the init code and reenable it afterward to make sure the NMI handler doesn't mess things up?

Yes, which is why it should be enabled prior to the rest of his routines, which is what my statement was implying. I'm seriously not going to sit around writing code for people. :P
Re: SNES dev newbie questions
by on (#126250)
juef wrote:
koitsu: thank you so much for your reply. I do have stz $2121 in Snes_Init but didn't bother recopying the entire contents of the routine as it's mostly the same as in this post, which is a file used by some tutorials I've found...


Look closely at your own code, and slow down. Think slowly.

Code:
Snes_Init

...
lda   #%11100000
sta   $2122
lda   #%00000011
sta   $2122
...


This effectively is setting the RGB value of colour #0 to whatever you've assigned via $2122 (I do not care to decode the BGR values right now, it's not the point). The reason it's affecting colour #0 is because you did stz $2121 in Snes_Init (which is correct/fine).

Now look a few lines forward, where you do this -- WITHOUT setting/touching $2121 prior:

Code:
...
lda   #%00011111
sta   $2122
lda   #%00000000
sta   $2122
...


What this is affecting is colour #1 in the palette, not colour #0 like you might think. This is because you didn't re-set/change $2121.

Basically what I'm saying is this, speaking using code:

Code:
stz $2121    ; Next writes to $2122 affect colour #0

lda #$whatever
sta $2122
lda #$whatever
sta $2122

lda #$whatever  ; These writes affect colour #1
sta $2122
lda #$whatever
sta $2122

lda #$whatever  ; These writes affect colour #2
sta $2122
lda #$whatever
sta $2122

...


Make sense? So you need to set $2121 to the colour you want to modify before touching $2122.
Re: SNES dev newbie questions
by on (#126251)
koitsu wrote:
What this is affecting is colour #1 in the palette, not colour #0 like you might think. This is because you didn't re-set/change $2121.

... oh! I had not realized that $2121 incremented after a write... I guess more doc reading is in order. Thank you very much once again for the very helpful reply, I got it working now.

DoNotWant wrote:
Basically, I changed ; to // for comments, changed the header, added a macro, and removed
some stuff. The macro takes a CPU address, which it turns into a ROM address.

This does seem to work, thank you!
Re: SNES dev newbie questions
by on (#126267)
juef wrote:
... oh! I had not realized that $2121 incremented after a write... I guess more doc reading is in order. Thank you very much once again for the very helpful reply, I got it working now.

Most PPU operations on the SNES work this way (and on the NES too). It's done for good reasons and is a life-saver when needing to send lots of data to the PPU sequentially/linearly. Happy coding!
Re: SNES dev newbie questions
by on (#126341)
Thanks again to everyone, I have been having a lot of fun playing around with SNES code. I was surprised how little time it took to find a case where emulation accuracy mattered! The SNES initialization routine I was using didn't clear VRAM properly, so while most emulators (including bsnes v077 and older) loaded my ROM "beautifully", more recent versions of bsnes/higan and real hardware showed junk aplenty.

Anyway, I have a few more quick questions. I have been trying to implement the most basic detector of MSU1 presence, unsuccessfully so far. Super Road Blaster's source helped a lot, but I'm missing something.
  • byuu's page on MSU1 specifications uses a memory mapping notation ($00-3f,80-bf:2000-2007) I'm not familiar with, despite some Googling. I would greatly appreciate some help with this.
  • Would a simple lda $2002 placed shortly after my SNES initialization routine read the first MSU1 presence identifier, or there is some... special magic to do before?

Thank you very much!
Re: SNES dev newbie questions
by on (#126342)
juef wrote:
  • byuu's page on MSU1 specifications uses a memory mapping notation ($00-3f,80-bf:2000-2007) I'm not familiar with, despite some Googling. I would greatly appreciate some help with this.

Wild guess here, but $00-$3F and $80-$BF looks like bank address (upper 8 bits of 24-bit address).

EDIT: Yes, it is. From fullsnes (nocash):
Code:
Overall Memory Map
  Bank    Offset       Content                                      Speed
  00h-3Fh:0000h-7FFFh  System Area (8K WRAM, I/O Ports, Expansion)  see below
  00h-3Fh:8000h-FFFFh  WS1 LoROM (max 2048 Kbytes) (64x32K)         3.58MHz
     (00h:FFE0h-FFFFh) CPU Exception Vectors (Reset,Irq,Nmi,etc.)   3.58MHz
  40h-7Dh:0000h-FFFFh  WS1 HiROM (max 3968 Kbytes) (62x64K)         3.58MHz
  7Eh-7Fh:0000h-FFFFh  WRAM (Work RAM, 128 Kbytes) (2x64K)          3.58MHz
  80h-BFh:0000h-7FFFh  System Area (8K WRAM, I/O Ports, Expansion)  see below
  80h-BFh:8000h-FFFFh  WS2 LoROM (max 2048 Kbytes) (64x32K)         max 2.68MHz
  C0h-FFh:0000h-FFFFh  WS2 HiROM (max 4096 Kbytes) (64x64K)         max 2.68MHz

The MSU maps in the system area as it should. The $2000-$20FF is unused and free to use by any expansion.
Re: SNES dev newbie questions
by on (#126426)
Thank you for your reply! I see that it's indeed unused, but I'm not quite sure if I understand correctly: if $00-$3F and $80-$BF are bank addresses, does that mean that there are actually 128 banks on which the MSU1 registers are mirrored? That sounds quite a lot to me, so I guess I got something wrong.

Also, if $002002-$002008 is a correct address for reading the MSU1 presence identificator, I must be doing something terribly wrong because I couldn't read these bytes correctly to save my life. Aside from Neviksti's InitSNES file, I barely have any code and yet can't manage to read those bytes as identified by snes9x's debugger's RAM viewer. The code below, which basically seems to loop through the 6 bytes to check, is mostly taken from the Super Road Blaster source:

Code:
.define MSU_ID $2002   ; 2002-2007 must return "S-MSU1"

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

   Start:
      InitSNES
      
      rep #$31
      sep #$20
      ldx.w #0
      l1:   lda.l MSU_ID,x
         cmp.l msu1HardwareIdentifier,x
         bne NoMSU1
         inx
         cpx #msu1HardwareIdentifier.end-msu1HardwareIdentifier
         bne l1

...

.Section "msu1HardwareString" superfree
   msu1HardwareIdentifier:
      .db "S-MSU1"
   msu1HardwareIdentifier.end:
.ENDS

I have a last question for today. During the InitSNES routine (again from Neviksti), there's STA $420B, which clears some memory by doing a DMA transfer, from what I understand. Somehow, the snes9x debugger tells me that this single instruction clears $002000-$0020FF (among other addresses) completely. If theses addresses are what I think they are, is there any reason for clearing them (aren't they unused?)?.

Thank you again for your help and patience!
Re: SNES dev newbie questions
by on (#126434)
> if $00-$3F and $80-$BF are bank addresses, does that mean that there are actually 128 banks on which the MSU1 registers are mirrored? That sounds quite a lot to me

Yes, that's how the SNES works. Same deal for all your regular registers, $2100 and such. It's a problem when DBR.d6 is set, eg: lda #$40; pha; plb; stz $2100 <- does not write to display register, as $402100 is not an MMIO region, it's a CART (ROM) address.

> Also, if $002002-$002008 is a correct address for reading the MSU1 presence identificator, I must be doing something terribly wrong because I couldn't read these bytes correctly to save my life.

With higan v094, you need a manifest.bml file to specify that the MSU1 is present. With sd2snes, you need a gamename.msu file to specify that it's present.
Re: SNES dev newbie questions
by on (#126442)
Please just post the code to this InitSNES routine. I'm getting a little tired of it continually coming up in every post without seeing the actual code (not to mention what you brought up initially was called Snes_Init, now you're using something called InitSNES). If I review it and I see nothing wrong with it (going purely off of what official documentation mandates), then I really couldn't care less what an emulator "claims" it's doing in this particular case.
Re: SNES dev newbie questions
by on (#126447)
Thank you byuu, this is very helpful.

koitsu wrote:
Please just post the code to this InitSNES routine. I'm getting a little tired of it continually coming up in every post without seeing the actual code (not to mention what you brought up initially was called Snes_Init, now you're using something called InitSNES).

I'm sorry about not having posted it; I had figured the code was generally considered OK since it is used in different tutorials and written by "big names" of emulation, but you're right, that's definitely not a good assumption to make, and switching between the two files can be confusing. I used Snes_Init.asm first from this tutorial, but found it was missing stuff, so I switched to InitSNES.asm from these tutorials. You can download all my current project files here.

Again, I'm sorry for not posting everything before; I thought I'd try to post the most relevant code only so that reading my posts isn't too much of a chore. I'll post the whole thing from now on. Please forgive my language as well, I'm not very used to write in English.
Re: SNES dev newbie questions
by on (#126455)
What a clusterfuck (re: those init routines). No wonder you can't make up your mind! I now see your dilemma.

Snes_Init is *almost* right, meaning it's the closest to the official init routine Nintendo mandates, but it's still incorrect in some spots.

InitSNES goes about things in what I would call "rude", meaning it isn't easy to follow and it's not even necessary (I'll explain why). The file DOES contains some useful routines, such as the ClearPalette and ClearVRAM (kudos to those!), but the rest is what I call "programmer wank-off material" -- someone literally sat around trying to think up ways to "speed up the initialisation routine" when such isn't needed at all. It's pure jack-off material because this is only something you do once at RESET!

So in my opinion, you actually need a combination of the two. I will write this for you in a bit, I have a dental appointment to go to, and in especially bad health today, so it will take me some time.

P.S. -- Je suis désolé, mais je ne parle pas Français, but if you ever need something I've written in English turned into Quebecian French, let me know and I can have a friend of mine from Montreal translate. The diversity of this forum is one of the things that I find awesome.
Re: SNES dev newbie questions
by on (#126466)
koitsu wrote:
Snes_Init is *almost* right, meaning it's the closest to the official init routine Nintendo mandates, but it's still incorrect in some spots.

Really? I assumed it wasn't that close due to how concise it is, and the fact that I could figure out it wasn't always correct despite my extremely limited skills. As for InitSNES, I thought it wasn't that bad, but I can't say I understood every part of it. What boggles my mind is that despite the many SNES dev tutorials available and some great homebrew such as Super Road Blaster, there isn't a single initialization routine that is generally accepted and praised, now that the SNES is rather well understood.

Your offer to write a combination of the two is much appreciated, thank you!

koitsu wrote:
if you ever need something I've written in English turned into Quebecian French, let me know and I can have a friend of mine from Montreal translate.

Thanks for the offer! I like to believe I am doing alright. It's just that I strive to be as respectful and appropriate as possible, and sometimes I feel that my lack of vocabulary is a barrier to this goal, especially online, where tone and intent interpretation can go wrong so easily.
Re: SNES dev newbie questions
by on (#126468)
Here's my version:

https://www.dropbox.com/s/s641i1p19du57 ... koitsu.txt

I would strongly suggest byuu review it, as it's been a while since I've written 65816, and I will tell you this is not my best (especially the ClearRAM routine, which could be sped up by 2x if lengths were halfed, writes used 16-bit accumulator, and inx/cpx was used instead of ldx/dex/bpl). I'm already 30 minutes past start of work so I'm in a hurry anyway. I'm certain I missed something in the "ClearXXX" routines, but I can assure you the register initialisation at the start is done 100% to-the-book.

P.S. -- Your English is fantastic, and you have an excellent vocabulary. I had no idea you natively spoke French until you mentioned ESL (English as a Second Language) and were from Quebec. So don't worry :-)
Re: SNES dev newbie questions
by on (#126482)
Great, thank you very much! This is a delight to read. I have a few questions / comments:

1) Typo on line 282's comment, should be 7f (in case you intend on reusing that file).

2) In ClearRAM, is there a reason for clearing $000000-$001fff and then $7e2000-$7effff rather than simply clearing $7e0000-$7effff? If I understand mirroring correctly, this would have the exact same result and allow the removal of a few lines of code.

3) The code seems to run perfectly until ClearRAM. I have tried my best to understand what's going on, but I failed miserably at that. Here is snes9x's debugger's output for the relevant part (by the way, is there a more interesting debugger to use nowadays?):

Code:
$00/819D A9 7E       LDA #$7E                A:0000 X:FFFF Y:0000 P:eNvMxdIzC
$00/819F 48          PHA                     A:007E X:FFFF Y:0000 P:envMxdIzC
$00/81A0 AB          PLB                     A:007E X:FFFF Y:0000 P:envMxdIzC
$00/81A1 A2 FF DF    LDX #$DFFF              A:007E X:FFFF Y:0000 P:envMxdIzC
$00/81A4 9D 00 20    STA $2000,x[$7E:FFFF]   A:007E X:DFFF Y:0000 P:eNvMxdIzC
$00/81A7 CA          DEX                     A:007E X:DFFF Y:0000 P:eNvMxdIzC
$00/81A8 10 FA       BPL $FA    [$81A4]      A:007E X:DFFE Y:0000 P:eNvMxdIzC
$00/81AA A9 7F       LDA #$7F                A:007E X:DFFE Y:0000 P:eNvMxdIzC
$00/81AC 48          PHA                     A:007F X:DFFE Y:0000 P:envMxdIzC
$00/81AD AB          PLB                     A:007F X:DFFE Y:0000 P:envMxdIzC
$00/81AE A2 FF FF    LDX #$FFFF              A:007F X:DFFE Y:0000 P:envMxdIzC          <----
$00/81B1 95 00       STA $00,x  [$00:FFFF]   A:007F X:FFFF Y:0000 P:eNvMxdIzC          <----
$00/81B3 CA          DEX                     A:007F X:FFFF Y:0000 P:eNvMxdIzC
$00/81B4 10 FB       BPL $FB    [$81B1]      A:007F X:FFFE Y:0000 P:eNvMxdIzC
$00/81B6 28          PLP                     A:007F X:FFFE Y:0000 P:eNvMxdIzC
$00/81B7 AB          PLB                     A:007F X:FFFE Y:0000 P:envmxdizc
$00/81B8 FA          PLX                     A:007F X:FFFE Y:0000 P:envmxdiZc
$00/81B9 68          PLA                     A:007F X:0000 Y:0000 P:envmxdiZc
$00/81BA 60          RTS                     A:0000 X:0000 Y:0000 P:envmxdiZc          <----
$00/0001 00 00       BRK #$00                A:0000 X:0000 Y:0000 P:envmxdiZc          <----
$00/81F5 40          RTI                     A:0000 X:0000 Y:0000 P:envmxdIZc
$03/0303 00 00       BRK #$00                A:0000 X:0000 Y:0000 P:envmxdiZc
$00/81F5 40          RTI                     A:0000 X:0000 Y:0000 P:envmxdIZc

The whole project's code can be found here. Thank you once again!
Re: SNES dev newbie questions
by on (#126486)
juef wrote:
2) In ClearRAM, is there a reason for clearing $000000-$001fff and then $7e2000-$7effff rather than simply clearing $7e0000-$7effff? If I understand mirroring correctly, this would have the exact same result and allow the removal of a few lines of code.

You're correct -- I missed the brief diagram comment in the official docs that indicates $7E, $0000-1FFF is effectively mirrored throughout banks $00-3F (same address range). So yes, that should simplify the routine greatly.

The SNES memory map is a general gigantic clusterfuck, so consider yourself warned. ;-)

Quote:
Code:
$00/81AA A9 7F       LDA #$7F                A:007E X:DFFE Y:0000 P:eNvMxdIzC
$00/81AC 48          PHA                     A:007F X:DFFE Y:0000 P:envMxdIzC
$00/81AD AB          PLB                     A:007F X:DFFE Y:0000 P:envMxdIzC
$00/81AE A2 FF FF    LDX #$FFFF              A:007F X:DFFE Y:0000 P:envMxdIzC          <----
$00/81B1 95 00       STA $00,x  [$00:FFFF]   A:007F X:FFFF Y:0000 P:eNvMxdIzC          <----
$00/81B3 CA          DEX                     A:007F X:FFFF Y:0000 P:eNvMxdIzC
$00/81B4 10 FB       BPL $FB    [$81B1]      A:007F X:FFFE Y:0000 P:eNvMxdIzC

Based on what I'm seeing above, it looks as if your assembler took sta $0000,x (assembled bytes should be: 9d 00 00) and thought it would be intelligent to "optimise" that into sta $00,x (assembled bytes: a5 00)

The reason it did because it thought by referring to $0000, that the intended goal was to use direct page, which is not the case here. Nothing pisses me off more than assemblers which try to out-smart the programmer... :-)

I don't know what assembler you're using, but there is probably a way to tell it "by $0000 I mean the absolute 16-bit address, not the direct page address". The mnemonic for that varies per assembler; it could be :, <, >, or who knows what. Your assembler should have documentation telling you how to do this.

You can verify all this if your assembler generates what's called a "code listing". It is ALWAYS wise to generate a code listing when starting new projects or learning. ALWAYS.

BTW, the ClearVRAM routine would have the same problem. You can clearly see me saying stx $0000 there, so it too would need to be changed. It should assemble to bytes 8e 00 00, not 86 00.

You will probably need to remember this situation any time you try to use an absolute 16-bit address that is within the range of $0000 to $00ff.

I've updated the content at my aforementioned Dropbox URL to reflect all of this, particularly by adding comments that indicate what SHOULD be happening, but obviously will vary depending on what assembler you use (not my responsibility to cater to that. :-) ).

Edit: I just realised why the BRKs occur after the RTS. I was staring at that a while going "eh?" *laugh* Silly, silly me: ClearRAM stomps over the RAM used for stack space ($1FFF going downward). So ClearRAM really needs to be made into its own macro (not subroutine) or JMP/JML-based, avoiding use of the stack entirely. I've modified mine to just use a pair of JMP statements (JMP'ing into the routine, and at the end JMP'ing back out of it).
Re: SNES dev newbie questions
by on (#126522)
koitsu wrote:
I don't know what assembler you're using, but there is probably a way to tell it "by $0000 I mean the absolute 16-bit address, not the direct page address". The mnemonic for that varies per assembler; it could be :, <, >, or who knows what. Your assembler should have documentation telling you how to do this. [...] You will probably need to remember this situation any time you try to use an absolute 16-bit address that is within the range of $0000 to $00ff.

I have been using WLA DX's latest version (can't say I'm a huge fan so far, but with my very limited assembly experience, I can't really be picky). It seems that sta.w $0000,x assembles correctly. Thanks!

koitsu wrote:
I've updated the content at my aforementioned Dropbox URL to reflect all of this, particularly by adding comments that indicate what SHOULD be happening, but obviously will vary depending on what assembler you use (not my responsibility to cater to that. :-) ).

Edit: I just realised why the BRKs occur after the RTS. I was staring at that a while going "eh?" *laugh* Silly, silly me: ClearRAM stomps over the RAM used for stack space ($1FFF going downward). So ClearRAM really needs to be made into its own macro (not subroutine) or JMP/JML-based, avoiding use of the stack entirely. I've modified mine to just use a pair of JMP statements (JMP'ing into the routine, and at the end JMP'ing back out of it).

I'm glad you figured that out because I'm not sure I would have thought about the stack! Thanks again very much. There's still a problem I haven't figured out but I haven't had the chance to look at it thoroughly yet: the loops in ClearRAM still only dex once before branching (pretty much the same as the first two arrows in the debugging log from this post). I'll take a deeper look later this weekend. For reference, my current code is here.

Oh, and another question regarding your RAM clearing routines. Considering these new modifications, is there a point to the phx and plx in ClearVRAM, ClearPalette and ClearOAM? ClearRAM, which is run last, can't use the stack and overwrites the X value so that we can use it as an index.

koitsu wrote:
The SNES memory map is a general gigantic clusterfuck, so consider yourself warned. ;-) [...] Nothing pisses me off more than assemblers which try to out-smart the programmer... :-)

I'm truly no expert, but so far I tend to agree with these statements :)
Re: SNES dev newbie questions
by on (#126526)
juef wrote:
I'm glad you figured that out because I'm not sure I would have thought about the stack! Thanks again very much. There's still a problem I haven't figured out but I haven't had the chance to look at it thoroughly yet: the loops in ClearRAM still only dex once before branching (pretty much the same as the first two arrows in the debugging log from this post). I'll take a deeper look later this weekend. For reference, my current code is here.

Actually, it's obvious to me why. A value of $FFFF would have the negative flag set, as would $FFFE, so that's why the loop only happens once. You can see it in the snes9x debugger CPU flags -- when n becomes N (little n means n=0, big N means n=1). TL;DR -- It's a bug as a result of me not doing 65xxx as often as I used to.

I've changed the loop once more, and it should be more obvious to you what's going on: ldx #0 / Loop: stz.w $0000,x / inx / bne Loop, which should write 65536 bytes.

I also found another bug in ClearRAM: I was using sta to zero out the memory contents (so I was actually writing the bank number across each bank of RAM), not stz. That was purely a typo on my part (a and z keys are next to one another).

I've also changed all the 16-bit absolute references to use the WLA DX .w modifier, for your benefit. It makes the visual layout stupid, since the opcodes no longer are clearly aligned in a column, so I had to space things out a bit. *sigh* Another reason I hate that kind of modifier being done in the opcode area and not the operand area. Matter of personal preference though.

juef wrote:
Oh, and another question regarding your RAM clearing routines. Considering these new modifications, is there a point to the phx and plx in ClearVRAM, ClearPalette and ClearOAM? ClearRAM, which is run last, can't use the stack and overwrites the X value so that we can use it as an index.

This is purely a matter of pedantry (on the part of the person who wrote the routines originally (not me)). It looks to me like the intended goal was to make the routines so that they could be used at any time (not just during reset), which is perfectly okay. Although ClearVRAM does make use of direct page addresses $0000 and $0001 (it stores a value of $0000 there, which is what the DMA routine uses as the value to write all across VRAM).

You're welcome to change them so that they don't save/restore registers, turn them into macros, make them jsl-able (using jsl/rtl instead of jsr/rts) or do whatever you want with them. The point is that the initialisation routines should do the Right Thing(tm) and not a mix-match of messy nonsense like the other two routines/code sets you were using previously.

I've updated my Dropbox stuff as usual.
Re: SNES dev newbie questions
by on (#126537)
> I would strongly suggest byuu review it

That should be fine. I use more DMAs and short loops to clear memory and registers in my own routine. But that's more because it runs on a 2KB SRAM chip (dev cart) that also houses a bunch of serial UART functions. But for ROM usage, yours should get the job done.

For any aspiring sound developer, note that the SPC700 RAM (aside from the stack by the IPLROM) is also uninitialized, and you should probably flush all the SMP/DSP registers as well.
Re: SNES dev newbie questions
by on (#126542)
I found a couple minor typos:
Quote:
; If using high-speed mode (3.58MHz), you need to use a long (24-bit)
; JMP to set the "B" register (Program Bank) to bank $80 or above.

I thought the program bank was K and the data bank was B.

Quote:
; L=$00, H=$01 (no explanation given by Nintendo)

This sets up the mode 7 matrix as the identity matrix.

Quote:
; JMP because the routine zeros out the area of RAM where the stack lives.

If this routine is entered and left by jumping to a constant address, why not just save two JMPs and inline ClearRAM?

It should also be possible to save 3 bytes in ClearPalette by using 8-bit XY.
Re: SNES dev newbie questions
by on (#126561)
1. Thanks for the typo fix, tepples. Yes it should be "K".

2. Yes, I'm aware of what the $211b and related mode 7 registers are, but I don't understand what makes matrix parameter A ($211b) and parameter D ($211e) special that they warrant being set to $0100. I've never done mode 7 stuff, and calculus formulas are not something I get anyway. :P

3. You're right (I didn't write that routine though ;-) ). As for inlining: like I said in my earlier posts, if the OP wants to change the routine to be a macro (inlining), fine by me, I really don't care/it's besides the point. I'm not going to get pedantic about it.

As for using DMA to clear memory: I didn't think this was possible until I found registers $2180/1/2/3 (I always wondered what the purpose of those was, now I know ;-) ).

One question for byuu: do you happen to know if the general DMA registers retain their contents after a DMA transfer (particularly registers $43x0 through $43x6) or are they changed/reset? With regards to RAM clearing, I was thinking of doing this to clear $7e0000 to $7fffff, but wasn't sure if I needed to re-set all the DMA registers before I did the 2nd transfer:

Code:
    rep   #$30      ; A=8, X/Y=16
    sep   #$20

    ldx   #$8008      ; $4300: $08: DMA mode: fixed source, write-once, A-bus->B-bus
    stx.w $4300      ; $4301: $80: DMA destination: register $2180
    lda   #$00
    ldx   #$0000
    stx.w $4302      ; DMA source address: $0000
    sta.w $4304      ; DMA source bank: $00
    stx.w $4305      ; DMA transfer size: 65536 writes (bytes)
    stx.w $2181      ; WRAM address (16 bits of 17-bit address):  $0000
    sta.w $2183      ; WRAM address (17th bit of 17-bit address): $00
    inc            ; A=$01
    sta.w $420b      ; Initiate DMA transfer -- should zero $7e:0000 to $7e:ffff
    sta.w $420b      ; Initiate DMA transfer -- should zero $7f:0000 to $7f:ffff

Sorry for the formatting errors, forum/code block doesn't particularly like literal tabs.
Re: SNES dev newbie questions
by on (#126564)
Typo: "the official SNES Developrs manual"

In linear algebra, an identity matrix looks like this:
Code:
[1 0
 0 1]

[1 0 0
 0 1 0
 0 0 1]

[1 0 0 0
 0 1 0 0
 0 0 1 0
 0 0 0 1]
etc.

Multiplying a vector by an identity matrix leaves the vector unchanged. Mode 7 is all one big pile of linear algebra, so it's probably best for the init code to put the matrix in "do nothing" mode initially, where only the scrolling registers do anything.

I'm not a lawyer, but I feel a need to get "because Nintendo said so" lines out of the way so that fewer people are tempted to seek out what are at least nominally trade-secret documents. Here's how I'd reword the notice in my own copy:
Code:
;
; The S-CPU's memory controller supports fast (3.58 MHz) ROM access
; only in banks $80-$FF.  Jumping out of bank $00 early in the reset,
; NMI, and IRQ handlers allows high-speed access on a cartridge
; configured for it.  Doing this in normal-speed mode doesn't hurt,
; so it's a win-win no matter what PCB layout/spec you're using.
;
Re: SNES dev newbie questions
by on (#126569)
> As for using DMA to clear memory: I didn't think this was possible until I found registers $2180/1/2/3 (I always wondered what the purpose of those was, now I know ;-) ).

Oh sorry, guess I should have posted examples. It's possible to clear everything with them: VRAM, OAM, CGRAM, WRAM. And even better to reuse most of the DMA setup between all four of your clears.

$2180 is also extremely useful for its auto-increment functionality. It's used all the time for streaming decompression and such.

> do you happen to know if the general DMA registers retain their contents after a DMA transfer (particularly registers $43x0 through $43x6) or are they changed/reset?

They are live registers used directly (eg no shadow registers or caching.) So $43x5-6 decrement visibly after each byte transfer, $43x2-3 increment (that's not a typo, $43x4 does not increment.) So you'll get back zero, unless the DMA was aborted mid-way (eg HDMA on the same channel runs.) $43x0-1 stay the way they were.

> With regards to RAM clearing, I was thinking of doing this to clear $7e0000 to $7fffff, but wasn't sure if I needed to re-set all the DMA registers before I did the 2nd transfer:

Reuse the same function code to initialize the DMA registers, and then for each clear function, call the stub, set the specific registers (eg for VRAM, set 2115, for WRAM, set 2181-3, etc) and transfer. Like you've said, being too aggressive with optimizations on cold code is never wise (eg your reset routine doesn't need sei, clc, cld. It can start with xce and work just fine. But yeah, have fun with your three saved bytes and less experienced people reporting it as a bug.) Instead, I adhere to "don't repeat yourself" to avoid accidental mistakes.

To throw a fun crazy idea at you ... one of my favorite 65816 tricks is static arguments. Your code looks like this:

Code:
jsr function
db $00, $01, $02, ...
nextInstructions:


function accesses the data after the jsr via lda $nn,s; and then it adds the size of the static parameters to the return address, so the rts returns after the static data.

Consider having a DMA transfer function like this:

Code:
jsr transferDMA; db 4300, 4301, 4302, 4303, 4304, 4305, 2115, 2116, 2117
//or use a pretty macro if you want


If nothing else, it'll confuse the hell out of people =)

> Sorry for the formatting errors, forum/code block doesn't particularly like literal tabs.

One of the reasons I gave up on requiring tabs for my extensible markup format was phpBB's poor handling of tabs. Sucks to paste an example, they copy it, and now it doesn't work.

And now I have people whose files break because they intermix tabs and spaces in a Python-like language that counts nesting level by indention level, and nobody on the planet can agree on how many spaces equals one tab. Fun.

> In linear algebra, an identity matrix looks like this:

Set the registers to zero on reset. When someone wants to use Mode 7, they can set the identity matrix to 1 themselves.

Deciding on default values would bring you into a rats nest of bike shedding. What about 2115? Surely #$80 is more common. What about add/sub settings? Shouldn't Vscroll account for line 0 being blank by default? Yeah, better to just say "everything's zero." and people can go from there. The only exception you really need for the PPU is $2100 display disable should certainly be on (technically, it's set on reset by hardware, so you can skip it if you want.) Clearly it should have been a display enable bit, but oh well. Not every design is elegant and well thought out.
Re: SNES dev newbie questions
by on (#126585)
koitsu wrote:
With regards to RAM clearing, I was thinking of doing this to clear $7e0000 to $7fffff, but wasn't sure if I needed to re-set all the DMA registers before I did the 2nd transfer:

I don't think that should work on an actual console, for $2180 superfamicom.org states:
Quote:
attempting a DMA from WRAM to this register will not work, WRAM will not be written. Attempting a DMA from this register to WRAM will similarly not work, the value written is (initially) the Open Bus value. In either case, the address in $2181-3 is not incremented.

I tested in geiger and no$sns, only worked in the latter (which is sort of unreliable in terms of accuracy). What you can do is put 0 in rom and use that as the DMA source.
Re: SNES dev newbie questions
by on (#126594)
Yeah, can't assert the same chip for reading and writing at the same time.
Re: SNES dev newbie questions
by on (#126597)
ARM9 wrote:
I don't think that should work on an actual console, for $2180 superfamicom.org states:
Quote:
attempting a DMA from WRAM to this register will not work, WRAM will not be written. Attempting a DMA from this register to WRAM will similarly not work, the value written is (initially) the Open Bus value. In either case, the address in $2181-3 is not incremented.

I've read this paragraph 4 times now, and it's bloody confusing. I can't reliably say I understand what it means, specifically the phrases "attempting a DMA from WRAM to this register" and "attempting a DMA from this register to WRAM". The only conclusion I've been able to reach is that the author of those lines is trying to say "Don't use general DMA to read from banks $7e/7f + write to $2180, and don't use general DMA to read from $2180 + write to banks $7e/7f", and if that's the case, then I can see some legitimacy in that.

But neither case is what my code was doing. The code uses general DMA to read from $000000 (fixed address) and writes to $2180. I'm aware of the RAM mirroring (banks $00-3f, ranges $0000-1fff, from banks $7e/7f). If what you're saying is that the above code won't work, then that brings into question byuu's statement that general DMA can be used to clear WRAM.

It's really good that we're discussing all this and probably confusing the hell out of the OP. *rolls eyes*
Re: SNES dev newbie questions
by on (#126599)
If you can think of a good place to split it, send me a PM.
Re: SNES dev newbie questions
by on (#126601)
Well, most of the questions I have been asking were about SNES initalisation, so perhaps the topic should simply be renamed. I can't say I understand all of this, but I haven't studied how DMA works at all yet. Thanks everyone for contributing to the discussion, I am learning a lot.
Re: SNES dev newbie questions
by on (#126602)
koitsu wrote:
I've read this paragraph 4 times now, and it's bloody confusing. I can't reliably say I understand what it means, specifically the phrases "attempting a DMA from WRAM to this register" and "attempting a DMA from this register to WRAM". The only conclusion I've been able to reach is that the author of those lines is trying to say "Don't use general DMA to read from banks $7e/7f + write to $2180, and don't use general DMA to read from $2180 + write to banks $7e/7f", and if that's the case, then I can see some legitimacy in that.

But neither case is what my code was doing. The code uses general DMA to read from $000000 (fixed address) and writes to $2180. I'm aware of the RAM mirroring (banks $00-3f, ranges $0000-1fff, from banks $7e/7f). If what you're saying is that the above code won't work, then that brings into question byuu's statement that general DMA can be used to clear WRAM.

It's really good that we're discussing all this and probably confusing the hell out of the OP. *rolls eyes*


The paragraph is saying "don't attempt to do general DMA from WRAM to WRAM." That includes the mirrored sections in banks $00-$3f and the WRAM banks $7e and $7f. In order to use general DMA to clear WRAM, you would have to store a zero value in the ROM itself somewhere (or possibly external RAM) and have the general DMA use that as the A-bus fixed source.

I'm with whoever wants to split this discussion into another thread, by the way.
Re: SNES dev newbie questions
by on (#126604)
doppleganger is correct.

The original quote is just a more verbose way of saying WRAM A <> WRAM B does not work, regardless of DMA transfer direction. Think about it from a hardware perspective: how can the same chip with one address+data bus be read from and written to at the same time at different locations?

So use an A-bus address ($43x2-4) that is not WRAM (nor a mirror of it.) ROM is the surefire method, but you could also do SRAM if you have some.
Re: SNES dev newbie questions
by on (#126605)
Do otherwise unused DMA registers work as a readable A-bus address for this?
Re: SNES dev newbie questions
by on (#126606)
Okay, then that also means the ClearVRAM routine written by Neviksti is wrong as well. *shakes head* So much broken code...

I'm going to generally bow out of assisting at this point. The reason has to do with the fact that all of this quickly becomes assembler-focused (meaning the code written has more and more of a dependency and requirement relating to the assembler being used and its related syntax requirements), and since I don't know WLA and find its documentation a bit of a mess, I choose to opt out. :D

The information being provided here is great/fantastic, however.
Re: SNES dev newbie questions
by on (#126607)
> Do otherwise unused DMA registers work as a readable A-bus address for this?

No, $21xx, $40xx, $41xx, $42xx, $43xx don't work. You either get open bus or 0x00 (depending on register and model) when you specify them as the A-bus address.

I don't even want to imagine how much "fun" you could have DMA'ing data directly to $420b (DMA enable)

> Okay, then that also means the ClearVRAM routine written by Neviksti is wrong as well.

ClearVRAM should work fine. It's transferring from WRAM to VRAM.

The problem is only with going from WRAM to WRAM.

> and since I don't know WLA and find its documentation a bit of a mess, I choose to opt out. :D

Unfortunately we can't all agree to use the same assembler. Even when we make our own, we often want to improve it and remove past mistakes. All I can recommend for writing truly portable code is to stick to straight opcodes and labels:

"lda #$00", "lda #$ffff" (no .b, .w), "label:" (no -/+, no .label, etc.)

Skip the macros, the directives, the defines, etc.
Re: SNES dev newbie questions
by on (#126773)
Thanks to everyone, I'm finally feeling like I'm getting things done! :D If anyone's curious, here's what I have so far:
  • MSU1 detection: screen turns green if MSU1 hardware is available, red otherwise. Includes the manifest and XML files to use with your favorite emulator or hardware MSU1 implementation.
  • Seizure trigger: WARNING, color changes every frame, epileptics beware!

The color one, I made mostly to play around with instructions, WRAM, and other things I'm not experienced with. Basically, every color has its intensity increased by 1, 2 and 3 respectively (looping around), one color per frame. I would very much appreciate if someone could give my code a quick check to see if there are obvious no-no's or code that could be simplified (I know not everything's optimal, I could save a few jumps here and there, but I included them just for clarity):

Code:
lda #$80            ; Bit 7 of $4200 must be set to enable NMI...
sta $4200
sep #%00110000      ; Set the A, X/Y registers to 8-bit.

lda #%00001111      ; Full brightness
sta $2100
wai

; Define color addresses so I don't mess up
.define GREEN       $0000
.define RED         $0001
.define BLUE        $0002
.define NEXTCOLOR   $0003   ; 0 = GREEN, 1 = RED, 2 = BLUE

; Set all colors to half intensity at first (because why not)
lda #%00010000
sta RED
sta BLUE
sta GREEN
   
Forever:

   stz $2121         ; We'll want BG0 only

   IncNextColor:      ; One color is updated per frame, find out which out for this iteration
      inc NEXTCOLOR
      lda NEXTCOLOR
      and #%00000011   ; This is a weird way I thought about to keep looping between 0 through 2
      sta NEXTCOLOR
      cmp #%00000011
      beq IncNextColor
   cmp #$00
   beq Update_Green
   cmp #$01
   beq Update_Red
   cmp #$02
   beq Update_Blue

   ; The following blocks increment the colors by 1, 2 and 3 respectively
   Update_Green:      ; low: #%11100000, high: #%00000011
   lda GREEN
   adc #%00000001
   and #%00011111      ; so that it's never above 31
   sta GREEN
   jmp Update_Color
   
   Update_Red:         ; low: #%00011111, high: #%00000000
   lda RED
   adc #%00000010
   and #%00011111
   sta RED
   jmp Update_Color
   
   Update_Blue:      ; low: #%00000000, high: #%01111100
   lda BLUE
   adc #%00000011
   and #%00011111
   sta BLUE
   jmp Update_Color
   
   Update_Color:      ; let's "build" the full color bytes
      lda GREEN
      asl a
      asl a
      asl a
      asl a
      asl a
      adc RED
      sta $2122      ; low byte (#%GGGRRRRR)
      lda GREEN
      lsr a
      lsr a
      lsr a
      ror a
      ror a
      adc BLUE
      rol a
      rol a
      sta $2122      ; high byte (#%0BBBBBGG)
      wai

jmp Forever
Re: SNES dev newbie questions
by on (#126779)
I think you're missing clc before your adc statements.
Re: SNES dev newbie questions
by on (#126780)
Indeed! Thank you.
Re: SNES dev newbie questions
by on (#127008)
Quoted from this topic:
byuu wrote:
It takes about 20 minutes to patch a game to use CD-audio: trace out writes to $214x, walk up the stack until you find it loading a track# and calling the "play track" function (every game I've looked at had one), and modify that routine to test if MSU1 is present. If so, play with MSU1. If not, call original function so the game still works just with the regular audio.

I successfully managed to get SMW playing PCM audio tracks with MSU1, but that was with the help from this document. I did try to trace writes to $214x beforehand, but there sure were a lot of those. Typically, how does one find out where the "play track" function is located? Or more generally, how does one discriminate "interesting" writes to $214x versus the others which do something else?

Other unrelated question: for another project, I used functions from this document by blargg to make sure the SNES DSP isn't muted. Since this is the only single thing I am interested in doing with the DSP right now, I was wondering if I overlooked a faster way to do this? It's not too bad, but it seems to me it's still a lot just to make sure a bit isn't set.

Thank you for reading!
Re: SNES dev newbie questions
by on (#127103)
Quick question about rotating bits in accumulator: in the following, I was expecting the 8-bit accumulator to become $80 after the final ROR, but it seems like the set bit just disappeared. This is the bottom code of this post.

Code:
$00/81EF AD 00 00    LDA $0000  [$00:0000]   A:0000 X:0000 Y:0000 P:envMXdiZc
$00/81F2 4A          LSR A                   A:0010 X:0000 Y:0000 P:envMXdizc
$00/81F3 4A          LSR A                   A:0008 X:0000 Y:0000 P:envMXdizc
$00/81F4 4A          LSR A                   A:0004 X:0000 Y:0000 P:envMXdizc
$00/81F5 6A          ROR A                   A:0002 X:0000 Y:0000 P:envMXdizc
$00/81F6 6A          ROR A                   A:0001 X:0000 Y:0000 P:envMXdizc
$00/81F7 2A          .....                   A:0000 X:0000 Y:0000 P:envMXdiZC

I understand that the carry flag becomes set, but I truly hoped for the bit to go around. What am I missing?
Re: SNES dev newbie questions
by on (#127104)
The result is correct. Think of the rotate instructions as "rotate operand and carry": if A is 8-bit, ROL/ROR rotate through 9 bits, A and carry. That means it requires 9 successive ROLs/RORs to get in A the original value, not 8. That's because the vacant bit gets the carry THEN the carry get the shifted-out bit. Example:
Code:
   lda #2  ; assume A is 8 bits.
   clc     ; A = $02, C = 0
   ror a   ; A = $01, C = 0
   ror a   ; A = $00, C = 1
   ror a   ; A = $80, C = 0
Re: SNES dev newbie questions
by on (#127105)
This makes perfect sense, thank you. The assembly tutorial I read didn't mention this, but then again it wasn't for 65816...
Re: SNES dev newbie questions
by on (#127106)
juef wrote:
This makes perfect sense, thank you. The assembly tutorial I read didn't mention this, but then again it wasn't for 65816...

In this case what's being described applies to 6502 and 65c02 as well. There is nothing different about ROR/ROL on the 65816 compared to the 6502/65c02.
Re: SNES dev newbie questions
by on (#127107)
A tutorial for another architecture might have described the ROL/ROR operations on non-65xx series CPUs, which tend to use the other kind of rotation that doesn't insert carry into the stream.

6502 ROL/ROR is like 68000 ROXL/ROXR or x86 RCL/RCR.
Re: SNES dev newbie questions
by on (#127108)
Indeed, the book is mostly about old x86 CPUs. Thank you all for the help!
Re: SNES dev newbie questions
by on (#127290)
So I managed to complete a few tests / demos, but I have yet again come upon a problem I'm not sure I quite understand.

This little project (full code here) only consists in displaying a picture. I used gfx2snes (a fork of pcx2snes I believe) on a 256px by 224px 8bpp PCX image, which resulted in tile data, a tilemap and palette. I chose to go with 16x16 tiles (because why not?) and stayed with 8bpp. The tile data was too big for one ROM bank so I put half of it in two different banks. Everything looks alright to me assembly-wise, but... I'll let screenshots from no$sns do the talking:

Original image:
Image

Video output (same for higan accuracy):
Image

BG1:
Image

Tile data:
Image

I did notice a few oddities when converting the image, however... gfx2snes (and presumably pcx2snes) inserted something like 64 null bytes at the beginning of the tile data...?! I stripped them and adjusted the code accordingly, without much change. Also, the tilemap it generates is quite huge: 2KB. I expected it to be (256*224) / (16*16) * 2 = 448 bytes. The Windows GUI version of eKid's pcx2snes did none of these, but the results were actually worse (which I guess isn't the tool's fault).

Any help / hint / advice would be greatly appreciated. Thank you!

[Edit] I'm still not sure why the generated tilemap is so big, nor why this doesn't work, but I got it working with pcx2snesWIN and 8x8 tiles rather than 16x16. I'll stick with that for now but I guess I know where to look should I decide to try 16x16 tiles again!
Re: SNES dev newbie questions
by on (#127298)
I did an update of gfx2snes to avoid 64x64 pix added for blank tile.
It is not yet commited on googlecode, sorry about that.
I will try to do this later this day.

*DONE* Last version of source code available