SNES dev newbie questions

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

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.
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Re: SNES dev newbie questions

Post by juef »

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.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

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 :-)
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Re: SNES dev newbie questions

Post by juef »

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: Select all

$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!
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

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. ;-)

Code: Select all

$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).
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Re: SNES dev newbie questions

Post by juef »

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 :)
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

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.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: SNES dev newbie questions

Post by Near »

> 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.
tepples
Posts: 22861
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: SNES dev newbie questions

Post by tepples »

I found a couple minor typos:
; 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.
; L=$00, H=$01 (no explanation given by Nintendo)
This sets up the mode 7 matrix as the identity matrix.
; 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.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

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: Select all

    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.
tepples
Posts: 22861
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: SNES dev newbie questions

Post by tepples »

Typo: "the official SNES Developrs manual"

In linear algebra, an identity matrix looks like this:

Code: Select all

[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: Select all

;
; 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.
;
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: SNES dev newbie questions

Post by Near »

> 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: Select all

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: Select all

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.
ARM9
Posts: 57
Joined: Sun Aug 11, 2013 6:07 am

Re: SNES dev newbie questions

Post by ARM9 »

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:
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.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Re: SNES dev newbie questions

Post by Near »

Yeah, can't assert the same chip for reading and writing at the same time.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

ARM9 wrote:I don't think that should work on an actual console, for $2180 superfamicom.org states:
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*
Post Reply