A simple sprite demo for teaching

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

Re: A simple sprite demo for teaching

Post by tepples »

So I split out two more files. Now before I push out a new version of NROM, SNROM, and LoROM, I'm just waiting for koitsu to rip me a new one with constructive criticism :?
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: A simple sprite demo for teaching

Post by koitsu »

tepples wrote:So I split out two more files. Now before I push out a new version of NROM, SNROM, and LoROM, I'm just waiting for koitsu to rip me a new one with constructive criticism :?
*laugh* You crack me up man. :P Good thing I've been drinking this evening.

I know this is kinda roundabout, but if you could download (in the other SNESdev thread, re: "issues with 16-bit indexing") the MetaspriteTestKoitsu stuff, that might give you some insights into how I tend to lay things out. The organisation there is not pristine, meaning it's not 100% how I'd implement stuff, but you'll get an idea of what I've changed + where my complaints stem from. The template was kind of weird anyway, I was often confused by things like why there was "zero page" yet it was a 65816 program (and after trying to implement "true direct page" I now understand why you did stuff like leave ZP from $10-FF, although IMO that should really be $00-FF. If there's a reason for $10-FF I'd love to know what it is!).

A lot of it has to do with overall formatting. Your comments and general organisation of code/directives are... compact? I'm not sure what word to use. I might use the word "lazy". Things aren't formatted very well (aligned spacing, etc.). I changed a bunch of that not only in the template but related files too. One of my main complaints is that in snesheader.s (?) you actually have your NMI handler and RESET handler code -- this should not be in that file. That file should be solely for either the SNES ROM header (as in $FFC0 or whatever), or the file format header (e.g. SMC/SFC file). That's what the filename implies. You'll see in what I implemented for Espozo that I stuck it into Main.asm with the rest of the code.

I'm not so sure I like the general layout of snes.h either. While describing each of the bits in an MMIO register the way you do is understandable (for quick reference), I imagine most people doing development fall into two categories: a) not knowing what the bits do + already have something open (PDF, etc.) that documents them, or b) know what the bits do and don't need the quick reference.

The names of the equates should also be the same as what's in the official SNES developers manual, IMO. Note: I myself am quite of not using those equate names, so I can't pass too much judgement, but it's a good habit to get into early on. You made a template/etc. for people to learn from, and I feel changing that to instil good habits from the start is worthwhile.

I would also strongly suggest that the file not be named with an .h suffix, as it implies C header syntax. I'm willing to bend on this, but I think Espozo also mentioned this. Things that get .included I tend to name either .asm or .inc (often opting for the former, because assembly code is assembly code, even if all it is is a huge bunch of FOO = $xxxx statements). But I'm flexible.

This is one of those projects where I wish I could just sit down with you in person for a couple days and clean everything up alongside you, and negotiate things in person. It goes a lot faster than online, as I'm sure you know.

All criticism aside: I'm REALLY thankful you made that lorom template. It took me some hours to sift through, but I wouldn't have been able to write that from scratch, and it allowed me to learn a lot about ca65 in the process. Though admittedly I had the ca65 and ld65 docs up for hours as well, especially ld65 -- "Who cares if there isn't a zeropage segment defined!!! It's direct page on 65816, jerk!" :-) Oh that reminds me: I was sure in my version to inline comment the addresses in the MEMORY section to actually correlate with what's in the actual 65816 code. I feel it's important to document there that, for example, if you declare a certain region in the MEMORY section that it should match what your code does using lda/tcd or ldx/txs (although for the latter (stack) I just gave up and left it within BSS. Admittedly, I still don't understand why BSS is even needed per se: I fully understand this for C, but for pure assembly on a console this kind of thing seems weird. But I think that's more of a ca65 "design" thing than the fault of... well... I'm not sure what. *laugh*)

Oh, and that reminds me: I wish there was some kind of programmatic way in ca65/ld65 to "get the address of a MEMORY section", so that you could actually implement that in code somehow. I can see exactly why this is difficult/troublesome, but it'd be nice to just be able to change the template file and then have your lda #xxxx/tcd statements correlate with what you changed. You'll see in the code I changed/wrote that I was meticulous in documenting the importance of that correlation. For my complaint about the stack (e.g. ldx/txs), it'd be weird because the stack starts at the end and works backwards; so an origin of $1e00 and a size of $100 would thus be ldx #$1fff/txs but that's not really "obvious" to a newcomer... sorry, rambling a bit here.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A simple sprite demo for teaching

Post by tepples »

koitsu wrote:if you could download (in the other SNESdev thread, re: "issues with 16-bit indexing") the MetaspriteTestKoitsu stuff, that might give you some insights into how I tend to lay things out.
If you're referring to this post, is there a reason you use .rar instead of the more widely supported .zip?
koitsu wrote:and after trying to implement "true direct page" I now understand why you did stuff like leave ZP from $10-FF, although IMO that should really be $00-FF. If there's a reason for $10-FF I'd love to know what it is!
I have added a comment to lorom256.cfg to address my reasoning behind this:

Code: Select all

  # I usually reserve $000000-$00000F for local variables
  # allocated just below the .proc statement of a subroutine.
  # The rest is open for global variables.
koitsu wrote:Things aren't formatted very well (aligned spacing, etc.).
Not sure what you mean by this. Are you referring to my habit of spacing inline comments from the code by 2 spaces instead of tabbing all the way out to column 41 or thereabouts?
koitsu wrote:One of my main complaints is that in snesheader.s (?) you actually have your NMI handler
The NMI handler is actually in main.s. What you're seeing here are handlers for the unused vectors. The S-CPU has no /ABORT signal, and the program doesn't use the /IRQ signal or BRK or COP instructions or 6502 emulation mode (except for the first couple instructions).

Code: Select all

cop_handler:
brk_handler:
abort_handler:
irq_handler:
ecop_handler:
eabort_handler:
enmi_handler:
eirq_handler:
  rti
Putting these in the same file as the vectors also means that most of the vectors don't have to be made global (except for nmi_vector and perhaps irq_vector if I end up using it).
koitsu wrote:You'll see in what I implemented for Espozo that I stuck [the init routine] into Main.asm with the rest of the code.
Which makes main.s even longer. I tried to separate it out because it's something the user doesn't need to touch quite as often.
Espozo wrote:another thing that would make the code a lot more "comprehendible" would be if you actually separated these into separate files.
koitsu wrote:While describing each of the bits in an MMIO register the way you do is understandable (for quick reference), I imagine most people doing development fall into two categories: a) not knowing what the bits do + already have something open (PDF, etc.) that documents them, or b) know what the bits do and don't need the quick reference.
I need the quick reference to save me from having to pull up Fullsnes all the time. You might notice I did the same with "pin8gba.h" back when I was homebrewing for GBA.
koitsu wrote:The names of the equates should also be the same as what's in the official SNES developers manual
I'm not officially supposed to have access to that manual, and I wanted to avoid the appearance of misappropriation.* So for most MMIO ports, I chose names that made sense to me in the context of the quick reference. You might notice that I left the HDMA-related names commented out because I haven't played with HDMA yet.
koitsu wrote:I wish there was some kind of programmatic way in ca65/ld65 to "get the address of a MEMORY section"
You can get the address of a segment. If you put define=yes, ld65 will export symbols for the segment's size, its run address (where the data is used), and its load address (where its data was stored in ROM, which doesn't exist in the case of BSS-type segments). In blarggapu.s, based on an example by (guess whom), I use this to send the SPC700's executable image to the SPC700 IPL.

I've attached a WIP version of the LoROM template with some but not all recommended changes made.


* The Uniform Trade Secrets Act, in effect in most U.S. states, defines "misappropriation" to include "disclosure or use of a trade secret of another without express or implied consent by a person who [...] at the time of disclosure or use, knew or had reason to know that his knowledge of the trade secret was [...] acquired under circumstances giving rise to a duty to maintain its secrecy or limit its use".
Attachments
lorom-template-0.05-pre.zip
(122.75 KiB) Downloaded 268 times
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: A simple sprite demo for teaching

Post by Myask »

There does appear to be a federal trade secrets law.

On the other hand [and I am not a lawyer] the register names don't seem like they're a "secret whose holding conveys economic advantage"
User avatar
whicker
Posts: 228
Joined: Sun Dec 13, 2009 11:37 am
Location: Wisconsin

Re: A simple sprite demo for teaching

Post by whicker »

if a trade secret is like a formula (percentage of ingredients proportional to each other), or a process (add this, then that, remove that, etc), manufacturing technique (some sort of sequence again), computer algorithm (sequence of instructions that produces a desired result), or survey method or "marketing strategy" or something patentable but not yet patented, I don't see how 6-character labels or whatever of internal registers matter in such a debate.

Honestly, it's past to the point where we need a retrogame homebrew association kind of thing to try and define legal rights, to stop this guessing game. Nobody seems to be getting rich off this hobby, and thus it's too expensive for individual representation. But yet it's an activity that people seem to want to engage in, for whatever reason...

Just a thought...



Also, based on what tepples has created, it actually does make a lot of sense to me to handle the interrupt vector table in the header. Personally I'd take it even further, in his naming logic, to have an init.s type file that includes the header, that gets the system going in a known (initialized) state then jumps to main. This is subject to debate, of course.

I am leery of splitting even more stuff out, other than the general memory to memory copy routines, because if we're talking about a simple example there is a mental burden in jumping between numerous source files trying to match up label references.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: A simple sprite demo for teaching

Post by thefox »

tepples wrote:
koitsu wrote:I wish there was some kind of programmatic way in ca65/ld65 to "get the address of a MEMORY section"
You can get the address of a segment. If you put define=yes, ld65 will export symbols for the segment's size, its run address (where the data is used), and its load address (where its data was stored in ROM, which doesn't exist in the case of BSS-type segments).
"define=yes" is also supported for MEMORY specifications.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: A simple sprite demo for teaching

Post by thefox »

koitsu wrote:I would also strongly suggest that the file not be named with an .h suffix, as it implies C header syntax. I'm willing to bend on this, but I think Espozo also mentioned this. Things that get .included I tend to name either .asm or .inc (often opting for the former, because assembly code is assembly code, even if all it is is a huge bunch of FOO = $xxxx statements). But I'm flexible.
".inc" might be a good choice here, given that cc65 library source code also uses it for its assembly include files (https://github.com/cc65/cc65/tree/master/asminc). One more advantage to not using ".h" is that text editors will have an easier job telling apart C and assembly source.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A simple sprite demo for teaching

Post by tepples »

Don't text editors already have trouble telling 6502-family assembly source from 8086-family assembly source?
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A simple sprite demo for teaching

Post by tepples »

So I got around to looking at koitsu's init code, and I'll summarize it in words before implementing it in my template.

A tiny stub in bank $00 needs to set interrupt priority to 1, turn off decimal mode, leave 6502 emulation mode, and long jump to the rest of init code in another bank. This should set 16-bit mode, set the stack pointer, set the data bank, set the direct page base address, and then load a predictable state into writable MMIO ports of the S-PPU and S-CPU. Here are the values found in koitsu's init code, with nonzero values explained to the best of my ability:

Code: Select all

2100=80  // Forced blanking
2101-2103=00
// Skip 2104: OAM write
2105-210C=00
210D-2115=00 00
2115=80  // Increment VRAM after high byte write
2116-2117=00
// Skip 2118: VRAM write
211A=00
211B=00 01  // Top left 1 in identity matrix for mode 7
211C-211D=00 00
211E=00 01  // Bottom right 1 in identity matrix for mode 7
211F-2120=00 00
2121=00
// Skip 2122: CGRAM write
2123-212F=00
4200=00
4201=FF
4202-420D=00
Unlike on the NES, you don't need to wait for 2 vblanks before clearing video memory:
  • VRAM: Fill 65536 bytes with 00
  • CGRAM: Fill with 00
  • OAM: Fill with F0 F0 00 00
  • High OAM: Fill with 00
Except for things that you want to survive a reset, it's recommended to clear WRAM to $00. The fast way is to write the address to $2181, then do two 65536-byte fixed-source DMAs from a byte in ROM to $2180. There have been holy wars about this subject in the past, with some people claiming that clearing helps hide failure to properly initialize memory at the start of each level.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: A simple sprite demo for teaching

Post by koitsu »

And for those who aren't sure... :P

Also, the reason I set $2180 to $80 instead of $8f is because I intentionally set the screen "as dark as possible" initially, as most of my stuff tended to fade screens in once init + VRAM setup + etc. was done. You can set it to $8f without any repercussions.
Attachments
magic.png
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A simple sprite demo for teaching

Post by tepples »

Thanks for all the help so far making this template better.

Let me paraphrase that scan: "These are useful defaults, but you don't have to use these exact values. If you know what you're doing, you can use other values. But make sure you write something to all writable ports before trying to do anything that depends on their values." Or even shorter: "Write something meaningful to each port." This encourages me to briefly describe what the result of the settings shall be.

Are the names in Fullsnes the same as the official names? If so, I can probably add them as aliases in a section titled "Fullsnes names".

I've changed the .h extensions to .inc. Let me explain why I chose .inc instead of .asm: When I used to post to forum.gbadev.org, certain people got in the bad habit of having main.c do #include "this.c", #include "that.c", etc. The best practice was to compile each .c file separately, and include only interface stuff (constants, struct declarations, extern global variable declarations, function prototypes, and possibly static inline functions) in .h files. With this C best practice firmly ingrained in my mind, I ended up developing an analogous habit of distinguishing files that get assembled into independent object files (.s) from those that are only included into other files (.inc, formerly .h).

I've also started to break what is now snes.inc into functional groups and indented the forward branches in the player code.

Otherwise, my next tasks before releasing 0.05 are as follows:
  • Break snes.inc by functional group
  • Add Fullsnes names to snes.inc
  • Briefly describe effects of default settings applied by init code
  • Backport these changes to NES versions
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: A simple sprite demo for teaching

Post by koitsu »

Well, the preceding paragraph is badly translated, so it's difficult (without seeing the Japanese docs) to know what exactly they're trying to say. It all sounds flexible up until the very end where it says "...and initial settings must be performed". The chapter is called "Register Clear (Initial Settings)", so the way I've always read the paragraph is: "no matter what you do later in the code, you need to make sure these exact values are written to their associated registers on reset (power-on)".

And as I've said before, since the routine only gets called one time (reset), "optimising" the routine (either for speed or length/size (loops), or flat out skipping some register initialisation because "you do it later") is absolutely 100% pointless. I strongly believe it's best to just have a straight set of lda/sta/stz code that sets these values, skipping ones like $2104, $2118/9, and $2122 (since you'll be doing those later), and use that followed by your own code. Yet over the years I have (honest!) encountered at least 6 different variations of this init routine, where people are overcomplicating it for no good reason.

Translation: stuff like this is absolutely ridiculous:

Code: Select all

  LDX #$2101
_Loop00:		;regs $2101-$210C
  STZ $00,X		;set Sprite,Character,Tile sizes to lowest, and set addresses to $0000
  INX
  CPX #$210D
  BNE _Loop00

_Loop01:		;regs $210D-$2114
  STZ $00,X		;Set all BG scroll values to $0000
  STZ $00,X
  INX
  CPX #$2115
  BNE _Loop01
...while this does the exact same thing. Again, remember, ROUTINE IS ONLY USED ONCE.

Code: Select all

  stz $2101
  stz $2102
  stz $2103
  stz $2104
  stz $2105
  stz $2106
  stz $2107
  stz $2108
  stz $2109
  stz $210a
  stz $210b
  stz $210c
  stz $210d
  stz $210d
  stz $210e
  stz $210e
  stz $210f
  stz $210f
  stz $2110
  stz $2110
  stz $2111
  stz $2111
  stz $2112
  stz $2112
  stz $2113               
  stz $2113               
  stz $2114
  stz $2114
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: A simple sprite demo for teaching

Post by tepples »

Don't worry; I'm not doing loops or any $#!+ like that. I'm doing the unrolled set of sta/stz as you recommend, just with the 16-bit writes (where consecutive registers both get a single $00) before the ones that need to be done 8-bit, and with direct page set to $4200 and $2100 like Square Enix games.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: A simple sprite demo for teaching

Post by koitsu »

Sounds fine to me. Just make sure that D gets set back to whatever it "should" be (ideally $0000) at the end or the like. Although this is where I bring up the thing I mentioned in the other thread, re: how I wish ca65 had better comprehension of direct page + a way to get the start address defined in the temple MEMORY area into code, so that you could just say something like lda #.LOWORD(MEMORY(DP))/tcd and be able to relocate things simply by changing the template.

Otherwise, well, the simple solution is -- just document it with a comment like I did in the code for Espozo. :-) "Make sure this correlates with what's in lorom256k.cfg".
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: The devil needs an advocate

Post by Myask »

This seems to be a bit of a hot-button issue for you. I wonder why.
koitsu wrote:Well, the preceding paragraph is badly translated, so it's difficult (without seeing the Japanese docs) to know what exactly they're trying to say. It all sounds flexible up until the very end where it says "...and initial settings must be performed". The chapter is called "Register Clear (Initial Settings)", so the way I've always read the paragraph is: "no matter what you do later in the code, you need to make sure these exact values are written to their associated registers on reset (power-on)".
And poor translation from Japanese to English can be horrendous, as I'm sure we all know.
Still, this seems a bit cargo-cult-programming-ish to me. You even named it "magic". (Yes, seems, not is; I looked at the docs and elsewhere it appears to underscore the need to clear the registers.)
koitsu wrote: The official docs don't include any code -- they just tell you what each register needs to be set to value-wise on reset. So no, it's simply people being ridiculous and for some reason thinking that this one-time-called routine deserves loops and other nonsense (like "don't bother initialising some registers because we set them in the near future anyway" -- WHO CARES, do the init exactly like Nintendo says, do it one time, and stop worrying about the rest!)
koitsu wrote:meaning it's the closest to the official init routine Nintendo mandates,
If all they have is register values, they have not mandated any kind of routine; they don't even mandate an order (in the page you posted).
If they're going to set them soon, then it is a waste to set them up first and there is no good reason to leave it in, save "limited programmer time".OK, skimmed, various other places appear to say "clear the registers first".
koitsu wrote:I WISH PEOPLE WOULD STOP SCREWING AROUND WITH THE INIT ROUTINES: THERE IS NOTHING TO FIX/OPTIMISE IN THEM. THEY ARE RUN *ONCE* DURING RESET/POWER-ON. JUST USE THE VALUES NINTENDO GIVES YOU IN THE OFFICIAL DOCS AND BE DONE WITH IT. YOU DO NOT NEED LOOPS ETC. (THOSE ARE JUST SLOWER THAN UNROLLED) AND ALL IT DOES IS OBFUSCATE THE CODE. PLEASE STOP WRITING INIT ROUTINES OR "OPTIMISING THEM".
koitsu wrote:Again, remember, ROUTINE IS ONLY USED ONCE.
So, unlike a routine that gets run incredibly often multiple times, it has the worst possible speed payoff for unrolling. (Well, there is code that NEVER gets run, but...) Normally, unrolled loops and heavy-duty for-time optimization are for either a. time-critical code (like in VBLANK on NES) and b. oft-called code. Initialization is neither, and tens of loop iterations are going to lose negligible time. And, if we really are going to insist on having the best possible init routine,
koitsu wrote:And yes, there is some redundant code in there
then redundant loads are not going to be the way.

So, reasons people would write their own init routines (with loops):
  • Not using someone else's code or concept, for either IP reasons, (that is, so they don't have to find you/license your code/credit you/pay you/get sued by you, depending on how paranoid they are)
  • -or so that they have no black box, cargo-cult programming.
  • -or so they have a personalized understanding of their program; doing generally leads to more learning than reading.
  • Practice writing code in this new environment- including
  • ...what registers are where. (Certainly not something happening with un-named register addresses...but see below.)
  • If the routine will be included in all projects, then it would probably be better not to screw with omitting values, but...that means it's also now getting included in more SPACEs.
  • Loops are easier to program.
  • (style variance) Loops do not "obfuscate code"; they're a lot easier for me to read than that uncommented block of sta/stz you've got there.
Looking at SNESdevwiki, posting the SnesInit.asm snippet with the comments in naming the registers would have looked better than this block of raw stz's, too.

ed: I am curious what led you to believe it's badly translated.
Post Reply