- For making cartridges of your Super NES games, see Reproduction.
*laugh* You crack me up man. :P Good thing I've been drinking this evening.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 :?
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.
If you're referring to this post, is there a reason you use .rar instead of the more widely supported .zip?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.
I have added a comment to lorom256.cfg to address my reasoning behind this: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!
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.
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:Things aren't formatted very well (aligned spacing, etc.).
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).koitsu wrote:One of my main complaints is that in snesheader.s (?) you actually have your NMI handler
Code: Select all
cop_handler: brk_handler: abort_handler: irq_handler: ecop_handler: eabort_handler: enmi_handler: eirq_handler: rti
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.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.
Espozo wrote:another thing that would make the code a lot more "comprehendible" would be if you actually separated these into separate files.
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: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'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:The names of the equates should also be the same as what's in the official SNES developers manual
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.koitsu wrote:I wish there was some kind of programmatic way in ca65/ld65 to "get the address of a MEMORY section"
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".
- (122.75 KiB) Downloaded 174 times
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.
"define=yes" is also supported for MEMORY specifications.tepples wrote: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).koitsu wrote:I wish there was some kind of programmatic way in ca65/ld65 to "get the address of a MEMORY section"
".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.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.
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
- VRAM: Fill 65536 bytes with 00
- CGRAM: Fill with 00
- OAM: Fill with F0 F0 00 00
- High OAM: Fill with 00
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.
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
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
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
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".
And poor translation from Japanese to English can be horrendous, as I'm sure we all know.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)".
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!)
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).koitsu wrote:meaning it's the closest to the official init routine Nintendo mandates,
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".
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:Again, remember, ROUTINE IS ONLY USED ONCE.
then redundant loads are not going to be the way.koitsu wrote:And yes, there is some redundant code in there
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.
ed: I am curious what led you to believe it's badly translated.