A simple sprite demo for teaching

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
A simple sprite demo for teaching
by on (#134144)
I ported my minimal functional NES program to the Super NES. What bad practices did I use this time? And feel free to point out unclear things in the source code; in a beginners' example like this, I guess I need to err on the side of overcommenting somewhat.

Basic example (LoROM 2 Mbit) (0.05, updated 2015-02-13)
Updates via Git at pinobatch/lorom-template
Re: A simple sprite demo for teaching
by on (#134163)
tepples, I hope this feedback isn't going to be taken the wrong way: I know what you're asking in terms of the help you want, but the package as it stands literally requires such incredibly high technical knowledge of both Linux and Microsoft Windows, as well as build systems and an awareness of what a make file is, and GNU indoctrination, that as it stands anyone that can interpret it would already be able to copy/paste/borrow from documentation and commercial games the necessary ingredients to initialize a system as simple as the SNES.

Please don't throw anything out, your readme just needs a lot more explanation on setting up the build environment you have created, what each external program piece is doing, what is what, where to get the pieces, what to type, and things of that nature.

You're a funny person with some really dry humor. But most code comments I see contain quips that are not actually helpful.
This would actually bring me to tears of frustration on a bad day: "echo Files produced by build tools go here, but caulk goes where?"

"; 16x16 makes it a shitload easier to draw a player sprite" :facepalm:

Who is your intended audience. Maybe even state that in the text file?


Let's run this down as a typical n00b:
**I use Visual Studio in School to make programs, and now I wanna program a Mario game on SNES (or something)**
I downloaded the zip package.
I double click on readme.txt
it tells me in the text to use something other than notepad.
fine now i have to actually extract the zip package to a folder.
I now open readme.txt with notepad++

I reach the sentence "Building this demo requires cc65 for the PRG ROM, and Python and Python Imaging Library for the CHR ROM."
I am slightly puzzled.
I google "cc65 for the PRG ROM" as that is what it requires.
first google result takes me to "https://github.com/cc65/cc65/blob/master/libsrc/nes/crt0.s"
I am confused.
Go back to the readme file.
I read the sentence "You'll probably also need GNU Make and Coreutils, which Windows users can find in MSYS"
in MSYS?
"MSys accepts high-end technology challenges and delivers quality solutions for our clients operating in numerous domains. Our commitment to the clients' success made the clients to get a superior-level of confidence and partner with MSys"

---

Do I think the package you have is a very good start? Yes. Absolutely. A lot of work went into it.
Re: A simple sprite demo for teaching
by on (#134174)
Disclaimer: didn't download the ZIP.

whicker wrote:
I double click on readme.txt
it tells me in the text to use something other than notepad.
fine now i have to actually extract the zip package to a folder.
I now open readme.txt with notepad++

I sure can't be the only one who's upset with Notepad being like the only text editor left that doesn't understand non-CRLF newlines, right? I mean, seriously, even freaking edit.com could understand them ._.'

whicker wrote:
I reach the sentence "Building this demo requires cc65 for the PRG ROM, and Python and Python Imaging Library for the CHR ROM."
I am slightly puzzled.

I have the feeling this would be worse for somebody with a NES background (since the SNES does not have CHR-ROM, making the sentence actually misleading).

whicker wrote:
I read the sentence "You'll probably also need GNU Make and Coreutils, which Windows users can find in MSYS"
in MSYS?
"MSys accepts high-end technology challenges and delivers quality solutions for our clients operating in numerous domains. Our commitment to the clients' success made the clients to get a superior-level of confidence and partner with MSys"

This is worse for a Code::Blocks user: it comes with MinGW but not MSYS, so users get confused as hell (worse, that also tends to make MinGW makefiles completely unusable since they expect you to use MSYS). Doesn't help that even today I still don't know where to get the MSYS prebuilt binaries and no way I'd waste time building them myself (these days I run Ubuntu anyway so I just use the crosscompiler with the native shell).

Oh yeah, also if it uses MinGW, hope it's vanilla and not MinGW-w64, because Code::Blocks includes the former...
Re: A simple sprite demo for teaching
by on (#134180)
whicker wrote:
your readme just needs a lot more explanation on setting up the build environment you have created

Then let's get it started.

Code:
To install Make, Python 2, and Python Imaging Library under Ubuntu:

1. Open a terminal.
2. Type the following, followed by the Enter key:
   sudo apt-get install build-essential python-imaging
3. Type your password to authorize the installation.

To install Make, Python 2, and Python Imaging Library under Fedora:

I don't know

To install cc65 under Linux:

1. Visit https://github.com/cc65/cc65
2. Click Download ZIP
3. Can someone finish this?

To install Make under Windows:

1. Visit http://devkitpro.org/wiki/Getting_Started
2. Follow the instructions there to download and run the
   devkitPro Automated Installer
3. In the installer, check only MSYS.  Don't check devkitARM,
   devkitPPC, devkitPSP, or any of the libraries for newer platforms
   unless you want to start on one of those sometime soon.

To install Python under Windows:

1. Visit https://www.python.org/
2. Under Downloads, under Download for Windows, click Python 2.7.8
   (This step may change if the maintainers of the Python web site
    reorganize the Python web site.)
3. In your web browser's downloads folder, run python-2.7.8.msi
4. Follow the prompts through the installer wizard

To install Python Imaging Library under Windows:

1. Visit https://pypi.python.org/pypi/Pillow
2. Under Package, click the latest version
3. Click Downloads
4. Click Pillow-2.5.3.win32-py2.7.exe
3. In your web browser's downloads folder, run Pillow-2.5.3.win32-py2.7.exe
4. Follow the prompts through the installer wizard

To install cc65 under Windows:

1. Visit http://cc65.github.io/cc65/
2. Scroll to the bottom
3. Click "Windows Snapshot" to download the zip file.
4. Open the zip file.
5. Inside the zip file, open the bin folder.
6. Drag "ca65.exe" and "ld65.exe" into a new folder.
7. Add this folder to your "Path" environment variable.  Can someone finish this?

Open the makefile in a text editor and change EMU to whatever
Super NES emulator you have installed.


Quote:
This would actually bring me to tears of frustration on a bad day: "echo Files produced by build tools go here, but caulk goes where?"

I came up with that years ago when I discovered that some unzip tools could not create an empty folder. So I had to put some one-liner in a text file.

Quote:
"; 16x16 makes it a shitload easier to draw a player sprite" :facepalm:

Who is your intended audience.

At the second I wrote that, my conception of the target audience was people moving up from the NES to the Super NES. But ideally, it ought to be anyone who can use the command prompt. I've started to build a required reading list before one starts to develop for a retro console.

Quote:
**I use Visual Studio in School to make programs, and now I wanna program a Mario game on SNES (or something)**

If the audience is people who are used to clicking the Run button in a proprietary IDE and never seeing a terminal, then perhaps my audience is Christopher Pow so he can add support for some Super NES emulator to NESICIDE.

Sik wrote:
I have the feeling this would be worse for somebody with a NES background (since the SNES does not have CHR-ROM, making the sentence actually misleading).

Copy and paste problem. I may have to fix this in my SNROM template as well.

The Notepad bit was a reference to a comment by 3gengames.
Re: A simple sprite demo for teaching
by on (#134184)
You can ignore the comment, I'm a full-time Linux user and abuser now so I'm not too worried about Notepad's inept-ness. :)
Re: A simple sprite demo for teaching
by on (#134190)
installing Fedora dependences.
Code:
yum install make automake gcc gcc-c++ python python-imaging
Gotten from web searches, I haven't actually tested it. Does yum require root? Does install imply upgrade?

---

I just now reinstalled cc65 under Linux with standard build tools, and these are the steps I took:
3. unzip ZIP and in the following steps replace [path to cc65] with where you unzipped the folder.
4. In a terminal issue the following commands:
Code:
cd [path to cc65]
# there's no ./configure step
make
# non-root install because 'make install' complained about there being no prefix var. (case sensitive?)
make install prefix=~/usr

5. Insert the following in your .bash_profile or .bashrc file, to automatically add the local executables to your PATH
Code:
if [ -d "$HOME/usr/bin" ] ; then
    PATH="$HOME/usr/bin:$PATH"
fi


There is also a bunch of cc65 library files at ~/usr/share/cc65/ but your projects don't use them at all, so I ignored them.
Re: A simple sprite demo for teaching
by on (#134249)
Thanks for the feedback. I've addressed some of the concerns in README and added a .wav to .brr converter that I wrote. Let me know if I've missed something.
Re: A simple sprite demo for teaching
by on (#134344)
I did miss something. I introduced a numpy dependency in wav2brr that I plan to make optional later. But I'd still like a code and comment review of 0.02 in case anything is hard to understand, so that I have a better starting point for a larger (multiple bank) template and a HiROM template.
Re: A simple sprite demo for teaching
by on (#134588)
I eliminated the numpy dependency (by writing my own convolve function for preemphasis), corrected some errors in the link script, and improved the code comments. What else is needed?
Re: A simple sprite demo for teaching
by on (#134593)
Very nice.

Another option for controlling accumulator and index width is to use:
    .a8 .a16 .i8 .i16

edit: I probably should have looked to see where those macros were being used. Question: why wouldn't .a8 at the beginning of spc_begin_upload work?
Re: A simple sprite demo for teaching
by on (#134601)
I was under the impression that .a8, .a16, .i8, and .i16 only changed the assembler's output width for subsequent instructions, not the CPU's mode that lets it interpret the changed width correctly. The .smart directive lets the assembler watch SEP and REP instructions and generate appropriately wide following instructions.

At the start of spc_begin_upload, .a8 by itself wouldn't work because the following lda APU0 expects to already be in 8-bit mode.
Re: A simple sprite demo for teaching
by on (#134608)
tepples wrote:
I was under the impression that .a8, .a16, .i8, and .i16 only changed the assembler's output width for subsequent instructions, not the CPU's mode that lets it interpret the changed width correctly. The .smart directive lets the assembler watch SEP and REP instructions and generate appropriately wide following instructions.

Your impression is correct, Tepples.
Re: A simple sprite demo for teaching
by on (#135662)
Has anyone by chance got this to compile on Windows? I got Make, python, pillow etc installed. Also, ca65.exe and ld65.exe are in the root directory of all the unzipped files. However, when I try to run Make, it finds the makefile okay but I only get the following output:

ca65 src/snesheader.s -o obj/snes/snesheader.o
make: ca65: Command not found
make: *** [obj/snes/snesheader.o] Error 127
Re: A simple sprite demo for teaching
by on (#135669)
GNU Make, like the rest of the MSYS distribution, follows the UNIX rule of not including the current working directory in the executable search path unless you explicitly say so for security reasons. You can use one of two workarounds:
  1. What I did when I developed on Windows was unzip ca65 and ld65 to some folder and then put that folder on my Path. Path is an environment variable; search the web for windows x.x environment variables (with x.x replaced with the version of Windows running on your PC, such as XP or 7 or 8.1) to learn how to change your environment variables.
  2. Or you can modify the makefile to look for ca65 and ld65 in the current working directory. Change ca65 to ./ca65 and ld65 to ./ld65.
Re: A simple sprite demo for teaching
by on (#135698)
Thanks Tepples, that worked! Though now it's complaining that it can't find the bggfx.png file. It's there and all the paths look correct. But still I get one of those "usr/bin/env: python: No such file or directory" errors when it tries to run the pilbmp2nes.py script.
Re: A simple sprite demo for teaching
by on (#135699)
I don't have a Windows+MSYS+Python installation handy to troubleshoot it myself, but this user has the same problem you're having. You might need to put Python on whatever MSYS thinks is your PATH as well.
Re: A simple sprite demo for teaching
by on (#135757)
Got er! It ended up being pretty simple. Python doesn't automatically set up an Path environment variable when it's installed on Windows, so I did that myself in the Advanced System Settings under System.

I also had an issue with directory/executable naming for no$sns. I left the executable named that when it unzipped, and unzipped everything for no$sns into a directory called no$sns months ago when I started playing around with it. The Make script didn't like the $ character, so I just renamed the directory and executable and all was well.

The ROM has compiled and is now up and running fine. Thanks for taking the time to make and share this Tepples. I've been off and on experimenting with SNES for a few months now and a nice beginner example like this will be very fun/helpful to dig into.
Re: A simple sprite demo for teaching
by on (#140059)
Egg is on my face. I had forgotten to include an essential file. This has been corrected.
Re: A simple sprite demo for teaching
by on (#140256)
So it appears my code is unreadable.

In this post, koitsu wrote:
I finally got around to looking at this code. Ho-ly-shit. Really? God dude, I don't even know where to begin. The code itself is fine (sort of -- very bad init routine from the look of it)

"very bad init routine"
Does it write the right values to the right registers? Is accessing them using D at $2100 a deprecated practice?

Quote:
but it's nearly impossible to follow given formatting, things in files that don't make any sense (like why is the reset vector code in snesheader.s)

For the same reason that C libraries include a pile of code that runs before the jump to main() in "crt0.s".

Quote:
and a ton of other things. Can you honestly read this given the formatting and almost "inline" comments without any actual structure?

What sort of "structure" should have been used?
Re: A simple sprite demo for teaching
by on (#140257)
tepples wrote:
Quote:but it's nearly impossible to follow given formatting, things in files that don't make any sense (like why is the reset vector code in snesheader.s)For the same reason that C libraries include a pile of code that runs before the jump to main() in "crt0.s"

You know the primary programming language here isn't C, right? :wink: (Hell, I don't even know anything except ASM. Everything else might as well be scribbles for all I care.)

I thing one problem is that everything is shoehorned into only a couple of files. This is alright for if you made it and know where everything is, but if other people are looking at it, you might want to have things like the init code separate. You might want to look at Walker on the SNES starterkit. Its very organized and understandable, in my opinion.
Re: A simple sprite demo for teaching
by on (#140269)
I agree that a map of the source code would help. Might it look like the following?

Organization of the program

Include files
  • snes.h: Register definitions and some useful macros for the S-CPU
  • global.h: S-CPU global variable and function declarations
  • spc-ca65.inc: Macro library to produce SPC700 instructions
  • spc-65c02.inc: Macro library to use 65C02 syntax with the SPC700

Source code files (65816)
  • snesheader.s: Super NES internal header and initialization code
  • main.s: Main program
  • ppuclear.s: Useful low-level subroutines for interacting with the S-PPU
  • blarggapu.s: Send a sound driver to the S-SMP

Source code files (SPC700)
  • spcheader.s: Header for the .spc file; unused in .sfc
  • spcimage.s: Sound driver

(end)

If too much code is in main.s, tell me what you think should be split and why, and I'll consider it.
Re: A simple sprite demo for teaching
by on (#140270)
Yes. I'll look at main.s in a bit and tell you what to split.
Re: A simple sprite demo for teaching
by on (#140276)
I stood looking at main.s and Walker.asm, wondering how you could make it better when I just realized the oblivious...

Walker looks a lot more organized because it has huge barricades like this to separate different parts of the code. (The parts you jmp and jsr to.)

Code:
;==============================================================================
; main
;==============================================================================


Also, you need to change stuff like this:

Code:
  cmp #28
  bcs notHitLeft
  lda #28
  sta player_xhi
  stz player_dxlo
  beq doneWallCollision
notHitLeft:
  cmp #212
  bcc notHitRight
  lda #211
  sta player_xhi
  stz player_dxlo
notHitRight:
doneWallCollision:

Into this:

Code:
  cmp #28
  bcs notHitLeft
  lda #28
  sta player_xhi
  stz player_dxlo
  beq doneWallCollision

notHitLeft:
  cmp #212
  bcc notHitRight
  lda #211
  sta player_xhi
  stz player_dxlo

notHitRight:
doneWallCollision:

Why in the world do you even have two different places you can jump to if there's not a single thing in between them?

Also, you seem to have the code that moves the character and checks for collision smack dap in between the code that sets the BG mode and stuff (I think?) and some code that does something with the tile data. Walker kind of does this, but not quite as bad. I just think the main problem is that it is not a very clear where something ends and something starts. If you want to split the file, you could have all BG mode and BG tile stuff in a different file from the part where the controllers are read to make the character move, so it is a little more clear as to what is going on.

I might try to help you a bit on this latter, (I don't really want to use wla in its current buggy state, but I am.) but for now, you might just want to take a look at walker in the SNES starterkit.
Re: A simple sprite demo for teaching
by on (#140284)
Espozo wrote:
Why in the world do you even have two different places you can jump to if there's not a single thing in between them?

With two labels, you can put code between them later and it's still going to act correctly, and you don't need to remember to update the branches. If I had done that then Sliding Blaster wouldn't have ever had that crash bug.
Re: A simple sprite demo for teaching
by on (#140285)
I will talk with tepples later about the "quality" of his code.

For Espozo: I would not bother spending any time analysing this code in comparison to your own. What I have been working on since last night is converting your code to use ca65 (including all necessary .exes, an updated .bat, etc.). I've done most of the work at this point, but as you can see from the other thread, I'm obviously running into problems with ca65 which are certainly user error on my part, and I cannot progress further until I can talk to someone who actually has very in-depth familiarity with ca65.
Re: A simple sprite demo for teaching
by on (#140286)
Espozo wrote:
Why in the world do you even have two different places you can jump to if there's not a single thing in between them?

Got ninjaed by NovaSquirrel. Anyways, I do the same thing quite often, and it's to help with code clarity and maintainability.

Looks like there's a bug in that piece of code. beq doneWallCollision never jumps since A was loaded with 28, but the bcc notHitRight below it ends up picking up the slack. On top of that, I think it's a good practice to add a comment when you're purposefully using a conditional branch instruction for an unconditional branch, e.g. something like:

Code:
bne doneWallCollision ; jumps always


...or just use BRA since we're on 65816 :)
Re: A simple sprite demo for teaching
by on (#140293)
Thank you for taking the time to look at my code. Sometimes it's like pulling teeth to get a code review. Please bear with me while I try to understand the importance of each issue you uncovered.

Espozo wrote:
Walker looks a lot more organized because it has huge barricades like this to separate different parts of the code. (The parts you jmp and jsr to.)

Ideally, that's what .proc is for. But if .proc isn't noticeable in the default settings of other users' existing favorite text editors, then I might need to put doc comments on all subroutines, even subroutines whose behavior is evident from the name and which takes no arguments. Here's what a doc comment from ppuclear.s looks like:
Code:
;;
; Clears a nametable (2048 bytes) to a constant value.
; @param X starting address of nametable in VRAM (16-bit)
; @param Y value to write (16-bit)


Quote:
Also, you need to change stuff like this:

My coding convention often includes a blank line after a "skip forward" type label, roughly corresponding to the End If of an If ... Then ... End If block. You are correct that I was missing a blank line.

Quote:
Why in the world do you even have two different places you can jump to if there's not a single thing in between them?

Because the jumps are for different reasons, one for not having hit the right side wall, and one for being completely done with collision checks for this frame. It helps with inserting additional checks at the right place should they become needed.

And yes, I discovered the same bug that thefox mentioned (a never taken branch that was intended to be always taken), but my laptop's 2 1/2-year-old battery immediately dropped from 35% to like 8% before I could finish this post about having done so. I had changed a lda #$00 sta to stz because hey, 65816, but I forgot to change the following branch. Here's what it has become:

Code:
  ; Test for collision with side walls
  ; (in actual games, this would involve collision with a tile map)
  cmp #28
  bcs notHitLeft
  lda #28
  sta player_xhi
  stz player_dxlo
  bra doneWallCollision
notHitLeft:

  cmp #212
  bcc notHitRight
  lda #211
  sta player_xhi
  stz player_dxlo
notHitRight:

  ; Additional checks for collision, if needed, would go here.
doneWallCollision:



Would this make more sense if I were to indent the "then clause" stuff?
Code:
  ; Test for collision with side walls
  ; (in actual games, this would involve collision with a tile map)
  cmp #28
  bcs notHitLeft
    lda #28
    sta player_xhi
    stz player_dxlo
    bra doneWallCollision
  notHitLeft:

  cmp #212
  bcc notHitRight
    lda #211
    sta player_xhi
    stz player_dxlo
  notHitRight:

  ; Additional checks for collision, if needed, would go here.
doneWallCollision:



Quote:
Also, you seem to have the code that moves the character and checks for collision smack dap in between the code that sets the BG mode and stuff (I think?) and some code that does something with the tile data.

Current order of subroutine:
Top level functions: nmi_handler, main
"Game logic" subroutine: move_player
Graphics subroutines: draw_player_sprite, draw_bg
Graphics data

Quote:
I just think the main problem is that it is not a very clear where something ends and something starts.

In my ca65 coding convention, everything from .proc to .endproc can be pulled out and put into another file.

Let me know what other improvements I could add to make this more understandable, and then I can backport them to the NES version.
Re: A simple sprite demo for teaching
by on (#140299)
tepples wrote:
Would this make more sense if I were to indent the "then clause" stuff?

Yes.

tepples wrote:
Current order of subroutine:Top level functions: nmi_handler, main"Game logic" subroutine: move_playerGraphics subroutines: draw_player_sprite, draw_bgGraphics data

I know this may sound a bit crazy to you, but another thing that would make the code a lot more "comprehendible" would be if you actually separated these into separate files.

Sorry if I seem a bit like a jerk...
Re: A simple sprite demo for teaching
by on (#140307)
Are you talking one subroutine per file? I've seen that in libraries to allow the linker to remove unused subroutines. If not, then what level of granularity between that and the current state of the demo would be best?
Re: A simple sprite demo for teaching
by on (#140308)
tepples wrote:
Are you talking one subroutine per file? I've seen that in libraries to allow the linker to remove unused subroutines. If not, then what level of granularity between that and the current state of the demo would be best?

What? You don't have to put everything in a separate file, but maybe try taking the largest subroutine (Isn't it move_player?) and putting it by itself. It's also usually nice to have graphics have a separate file, because otherwise, you'll end up with a huge main file.
Re: A simple sprite demo for teaching
by on (#140316)
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 :?
Re: A simple sprite demo for teaching
by on (#140322)
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.
Re: A simple sprite demo for teaching
by on (#140335)
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:
  # 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:
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".
Re: A simple sprite demo for teaching
by on (#140339)
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"
Re: A simple sprite demo for teaching
by on (#140341)
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.
Re: A simple sprite demo for teaching
by on (#140371)
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.
Re: A simple sprite demo for teaching
by on (#140373)
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.
Re: A simple sprite demo for teaching
by on (#140397)
Don't text editors already have trouble telling 6502-family assembly source from 8086-family assembly source?
Re: A simple sprite demo for teaching
by on (#140666)
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:
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.
Re: A simple sprite demo for teaching
by on (#140675)
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.
Re: A simple sprite demo for teaching
by on (#140682)
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
Re: A simple sprite demo for teaching
by on (#140693)
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:
  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:
  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
Re: A simple sprite demo for teaching
by on (#140695)
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.
Re: A simple sprite demo for teaching
by on (#140698)
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".
Re: The devil needs an advocate
by on (#140703)
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.
Re: A simple sprite demo for teaching
by on (#140705)
tepples wrote:
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.
I don't know about anyone else, but a good chunk of the reference documents I've used have been scattered into my source code as comments just so that I don't have to keep looking them up constantly.
Myask wrote:
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.
Honestly I'm inclined to agree with Myask's point of view. The time difference is irrelevant but loops would save (a tiny amount of) precious rom space. For the purpose of an instructional example though I would say it's better to store them all manually if only so you can comment along the way and explain each one. They're both easy for me to read but for someone new I think the loops would be an unnecessary complication...

By the way, there's a lot of talk of using ca65 instead of wla around here. Is there something wrong with wla that I should know about?
Re: A simple sprite demo for teaching
by on (#140707)
Someone had problems lately. But I'm not sure if they were using up-to-date builds, which also got posted here lately.
Re: A simple sprite demo for teaching
by on (#140708)
Yes. The .base directive is broken and messes with hirom and, more importantly, look at what happened here: (according to my code, the registers that I stored information into are the same one I loaded from, but when WLA assembled it, it went to the wrong register for some reason unknown.)

psycopathicteen wrote:
I just learned something new. WLA is a total piece of crap.

Quote:
0082ea ldy #$0000 A:0000 X:0000 Y:0000 S:1fff D:0000 DB:00 nvMxdiZC V:230 H: 30
0082ed ldx #$8401 A:0000 X:0000 Y:0000 S:1fff D:0000 DB:00 nvMxdiZC V:230 H: 54
0082f0 sty $1808 [001808] A:0000 X:8401 Y:0000 S:1fff D:0000 DB:00 NvMxdizC V:230 H: 78
0082f3 stx $180a [00180a] A:0000 X:8401 Y:0000 S:1fff D:0000 DB:00 NvMxdizC V:230 H: 118
0082f6 jsr $820e [00820e] A:0000 X:8401 Y:0000 S:1fff D:0000 DB:00 NvMxdizC V:230 H: 158
00820e php A:0000 X:8401 Y:0000 S:1ffd D:0000 DB:00 NvMxdizC V:230 H: 204
00820f rep #$10 A:0000 X:8401 Y:0000 S:1ffc D:0000 DB:00 NvMxdizC V:230 H: 226
008211 sep #$20 A:0000 X:8401 Y:0000 S:1ffc D:0000 DB:00 NvMxdizC V:230 H: 248
008213 ldy $08 [000008] A:0000 X:8401 Y:0000 S:1ffc D:0000 DB:00 NvMxdizC V:230 H: 270
008215 ldx $0a [00000a] A:0000 X:8401 Y:0000 S:1ffc D:0000 DB:00 nvMxdiZC V:230 H: 302
008217 lda $00,x [000000] A:0000 X:0000 Y:0000 S:1ffc D:0000 DB:00 nvMxdiZC V:230 H: 334
008219 beq $822e [00822e] A:0000 X:0000 Y:0000 S:1ffc D:0000 DB:00 nvMxdiZC V:230 H: 364
00822e plp A:0000 X:0000 Y:0000 S:1ffc D:0000 DB:00 nvMxdiZC V:230 H: 386
00822f rts A:0000 X:0000 Y:0000 S:1ffd D:0000 DB:00 NvMxdizC V:230 H: 414

There's also some other minor things, but these are the main problems I've seen.

Quote:
Someone had problems lately. But I'm not sure if they were using up-to-date builds, which also got posted here lately.

That someone being me. And yes, I was using an up-to-date build, it's just that the aforementioned problems still haven't been fixed. :?
Re: A simple sprite demo for teaching
by on (#140710)
It's a "hot-button issue" because:

1) This kind of thing used to happen back in the 90s snesdev days, where "newcomers" would somehow latch on to (get overly obsessed with) the init routine and start "optimising it" even though it doesn't need optimising (just do the register sets like Nintendo says and be done with it). I'm not kidding when I say I must have seen this 20 times during the snesdev days, and a scary number recently as well (covered below),

2) There was a recent thread about it where a bunch of people showed up to try and "optimise" (and some routines flat out had bugs in them) -- to me, it's just another rehash of #1, 20+ years later,

3) The code being mentioned/referenced in a different thread contained two separate init routines (InitSNES.asm and InitSNES2.asm), where the comments indicate they're written by the same person, but I think one of them might be from bazz (fellow here on the forum), not sure (and if not, then that means there are at least 3 routines floating around), followed in that same thread by this (which I can't figure out -- maybe it was meant for another thread, I don't know, but once again focusing on the Init routine...), followed by even ANOTHER routine being written (this time using the lda/tcd technique -- I said fine, go ahead and do it because I'm just about finished with all of this, but there's just no point to doing it that way).

I want people to seriously look at the 16bit table indexing problem thread and actually take the time to look at what a newcomer to SNES development goes through. I want people to stop their brains, sit down, and imagine being Espozo. I want people to think about how frustrating it must be to get some code handed to you only to not understand a ton of it -- even an init routine -- because it's written "fancy" when it doesn't need to be (with regards to the init routine). Please do not forget the subject line of THIS thread! This is TRAINING MATERIAL! Keep it simple/clear!

Any arguments about "saving ROM space" I'll ignore simply because they're silly (just my opinion) -- unlike the NES/FC, there are very few SNES/SFC games that were extremely tight with ROM space and thus required the programmer to say "hey I know, I'll try to save some bytes by 'optimising the power-on init routine'". There are bigger fish to worry about. In those situations, the primary focus becomes "how can I decrease my data size (graphics, structures, etc.)" and often resulted in some simple form of compression (e.g. RLE) to deal with it. The init routine isn't a focus.

I hope this clears up why it's a "hot-button issue" for me. The power-on register init routine is something that a programmer should write once (and it's already been written, a zillion years ago, and does not need changing), total focus time maybe 30 seconds. If this was a discussion about VRAM clear/init, CGRAM clear/init, OAM clear/init, WRAM clear/init, then that's all fine and dandy (using DMA is usually the best way to nuke those though, given that you have to write a general DMA macro or subroutine for general use anyway). But the power-on/reset init is something that doesn't need this degree of focus, yet for whatever reason people latch on to it, and continue to latch on to it 20 years later, despite needing absolutely none of this focus.
Re: A simple sprite demo for teaching
by on (#140718)
Khaz wrote:
The time difference is irrelevant but loops would save (a tiny amount of) precious rom space.

ROM space was precious on Atari 2600. It's not quite as precious on Super NES, where the launch titles were literally 128 times as big. But that's why I used the Squenix D=$2100 (saves one byte per write) and 16-bit STZs (cuts about a quarter of the writes).

Quote:
For the purpose of an instructional example though I would say it's better to store them all manually if only so you can comment along the way and explain each one.

Here's the explanation I put in docs/init.txt:
Code:
A valid Super NES program must write to all writable ports in the
S-CPU I/O and S-PPU at the start of a program.  This way, the
machine starts in a known state.  The code in init.s establishes
the following initial state:

S-CPU registers

A, X, Y: unspecified
Program bank: main >> 16
Data bank: unspecified (programs might want to PHK PLB)
P: decimal mode off, width 16-bit
S: $01FF
D: $0000

S-CPU I/O

4200=00         Disable vblank NMI and htime/vtime IRQ
4201=FF         Set pin 6 of controller ports high
4202=00         Multiply 0 by 0
4203=00
4204=00         Divide 0 by 0 (OH SHI--)
4205=00
4206=00
4207=00         htime = top of picture
4208=00
4209=00         vtime = left side of scanline
420A=00
420B=00         Stop DMA copies
420C=00         Stop HDMA
420D=00         Access $808000-$FFFFFF as slow ROM

S-PPU

2100=80         Forced blanking
2101=00         Sprites 8x8 and 16x16, patterns at $0000-$1FFF
2102=00         OAM address: $0000
2103=00
2104: skip      OAM write port
2105=00         Background mode 0, all layers using 8x8 pixel tiles
2106=00         Mosaic off
2107=00         BG1 nametable at $0000, 1x1 screen
2108=00         BG2 nametable at $0000, 1x1 screen
2109=00         BG3 nametable at $0000, 1x1 screen
210A=00         BG4 nametable at $0000, 1x1 screen
210B=00         BG1 and BG2 tiles at $0000
210C=00         BG3 and BG4 tiles at $0000
210D=00 00      BG1 scroll at (0, 1).  The S-PPU skips the first line
210E=00 00      of the picture, so Y=0 means start at line 1 of BG.
210F=00 00      BG2 scroll at (0, 1)
2110=00 00
2111=00 00      BG3 scroll at (0, 1)
2112=00 00
2113=00 00      BG4 scroll at (0, 1)
2114=00 00
2115=80         Add 1 word to VRAM address after high byte write
2116=00         VRAM address starts at 0
2117=00
2118-9: skip    VRAM write port
211A=00
211B=00 01      Set the mode 7 matrix to the identity matrix
211C=00 00      [ 1.0  0.0 ]
211D=00 00      [ 0.0  1.0 ]
211E=00 01
211F=00 00      Mode 7 scroll at (0, 0)
2120=00 00
2121=00         CGRAM address = 0
2122: skip      CGRAM write port
2123=00         Disable windows on BG1 and BG2
2124=00         Disable windows on BG3 and BG4
2125=00         Disable windows on sprites and blending
2126=00         Window 1 left side = 0
2127=00         Window 1 right side = 0
2128=00         Window 2 left side = 0
2129=00         Window 2 right side = 0
212A=00         Combine background windows using OR logic
212B=00         Combine sprites and blending using OR logic
212C=00         Enable no layers on main screen
212D=00         Enable no layers on sub screen
212E=00         Disable no layers on main screen within the window
212F=00         Disable no layers on sub screen within the window
2130=30         Disable blending and 332 palette
2131=00         Disable blending for all layers
2132=E0         Set entire COLDATA to 0
2133=00         Disable interlace and pseudo-hires, 224 lines


Espozo wrote:
[In WLA,] The .base directive is broken and messes with hirom

So does this template, which is designed for LoROM. I want to make sure it's free of poor practices before I try making a HiROM version.

koitsu wrote:
If this was a discussion about VRAM clear/init, CGRAM clear/init, OAM clear/init, WRAM clear/init, then that's all fine and dandy

OAM and nametables are cleared in ppuclear.s. CGRAM and pattern tables aren't cleared, but colors and tiles are DMA'd into memory in bg.s (for background) and player.s (for sprites). WRAM clear is one of those holy wars; tokumaru probably has a different point of view from the inventors of C.
Re: A simple sprite demo for teaching
by on (#140720)
That doc file looks beautiful and wonderful, tepples. I fully wholeheartedly approve. Big, BIG thumbs up.

The only thing I think might be wrong in it is the program bank (K). That should be $00 on power-on. That's because the CPU starts up in 65c02 emulation mode. The programmer should sei/clc/xce/jml to the next instruction (this sets K). phk/plb after is also recommended as you state, but up to the programmer based on their needs.

Also, just a general thing, but you should also do a long jump inside of your NMI. It ensures that your NMI will execute out of bank $80, thus getting the benefits from fastrom (3.58MHz). I recommend people do this regardless if mode 20, mode 21, slowrom, or fastrom -- it's a good habit in general, and harmless in non-fastrom modes.

I've attached the official documentation on why both of these needed. The ~ (tilde) on the end of the JMP instructions are to indicate to the Nintendo-sanctioned assembler to use a 24-bit long jump, thus setting K. Edit: sorry, I guess both are technically "65816 things", it's just that I'm used to the memory map on the IIGS and I've never experimented with fastrom on the SNES. (Guilty admission: I used to think you could just toggle CPU speed through that one SNES MMIO register and "everything would work", it wasn't until I got access to developers docs that explained only certain banks operate at high speed)
Re: A simple sprite demo for teaching
by on (#140721)
You mean like this excerpt from my current init code?
Code:
; Make sure these conform to the linker script (e.g. lorom256.cfg).
ZEROPAGE_BASE = $0000
STACK_BASE = $0100
STACK_SIZE = $0100
LAST_STACK_ADDR = STACK_BASE + STACK_SIZE - 1

PPU_BASE = $2100
CPUIO_BASE = $4200

.segment "CODE"
.proc resetstub
  sei  ; turn off IRQs
  clc
  xce  ; turn off 6502 emulation mode
  jml reset_fastrom
.endproc

.segment "CODE7"
.proc reset_fastrom
  rep #$38   ; go to 16-bit binary mode
  ldx #LAST_STACK_ADDR
  txs        ; set the stack pointer

  ; Initialize the CPU I/O registers to predictable values
  lda #CPUIO_BASE     ; temporarily move direct page
  tcd
  ; 8< 8< 8< a bunch of stuff omitted >8 >8 >8

  lda #ZEROPAGE_BASE  ; return direct page to real zero page
  tcd
  jml main   ; this is why I say the program bank is main >> 16
.endproc


The Game Boy Advance has three different mirrors of ROM that can be set to operate at different speeds. It boots in slow ROM mode (wait states 4n/2s), and most games switch the main mirror to fast ROM (3n/1s). The SuperCard adapter doesn't support fast ROM, so writes to the speed control register actually have to be patched out. I'm not aware of any GBA game that uses fastest ROM (2n/1s), but later GBA flash cartridges support it for enhanced frame rate in 3D games.
Re: A simple sprite demo for teaching
by on (#140731)
Looks right to me. I was about to throw a tiny tantrum about resetstub being in segment CODE while reset_fastrom was in segment CODE7, but I understand why you did that (and I was going to write a rebuttal against it, but after some deep thought, I figured that actually makes the most overall sense). I'll assume that the template sets CODE7 to bank $80 or at least between bank $80-FF.

Just please remember that the lorom256k.cfg template I saw (when doing stuff for Espozo) used MAPPER_LOROM (mode $20, which is 100% okay -- banks $80-FF get mirrored through bank $00-7D, with $7E/7F lost because of WRAM), and ROMSPEED_200NS (not ROMSPEED_120NS, which I think you'd need to fix).

Other nitpicks:

1. Use of rep #$38, rather than cld/rep #$30. This is another example of ridiculous optimisation for little gain -- you save 1 byte and 2 cycles at the expensive of confusing a newbie. 98% of code out there is going to use rep/sep with $10/$20/$30 and nothing else. We've already seen evidence of this being confusing for Espozo, so clearing of d of P through rep rather than just doing cld is silly, IMO.

2. I thought you and thefox said that it was possible to get the address of a MEMORY or SEGMENTS label within code itself? If that's the case, then the STACK_BASE/STACK_SIZE/LAST_STACK_ADDR and ZEROPAGE_BASE stuff shouldn't exist -- you should just be able to use whatever magic there is to do the equivalent of (pseudo code coming up) ldx #(.SEGMENT(STACK)+.sizeof(.SEGMENT(STACK))-1)/txs, and lda #.SEGMENT(DIRECTPAGE)/tcd.

3. The register equates should end up in a separate file if at all possible, and as mentioned before (again your call), use the official SNES label designation. (No I don't like the names of some of them, but consistency is good)

So now onto the formatting, and some of the above integrated:

Code:
; Make sure these conform to the linker script (e.g. lorom256.cfg).
ZEROPAGE_BASE   = $0000
STACK_BASE      = $0100
STACK_SIZE      = $0100
LAST_STACK_ADDR = STACK_BASE + STACK_SIZE - 1

PPU_BASE        = $2100
CPUIO_BASE      = $4200

.segment "CODE"
.proc resetstub
  sei                 ; turn off IRQs
  clc
  xce                 ; turn off 6502 emulation mode
  cld                 ; disable decimal mode
  jml reset_fastrom
.endproc


.segment "CODE7"
.proc reset_fastrom
  rep #$30               ; A=16, X/Y=16
  ldx #LAST_STACK_ADDR
  txs                    ; set the stack pointer

  ; Initialize the CPU I/O registers to predictable values
  lda #CPUIO_BASE
  tcd                    ; temporarily move direct page to SNES MMIO region for fast MMIO access

  ; 8< 8< 8< a bunch of stuff omitted >8 >8 >8

  lda #ZEROPAGE_BASE
  tcd                    ; return direct page to real zero page
  jml main               ; this is why I say the program bank is main >> 16
.endproc

Key things changed here:

1. Spacing of equate/= assignments. These should line up, making it visually easy for the person to know what's what. You (to date) are the only 65xx programmer I've met who doesn't "align" his formatting. Hate to single you out, but I'm being honest. Your style is your style and if I was working with you on a project and you did most of the work when I started, I'd conform to your style no matter what (that's the approach I take when collaborating), but if it was from the start I would be screaming loudly over "just two spaces then a semicolon, who cares". It really doesn't flow well.

2. Comments should be aligned as best as possible. The way I do this is the following: in the "general area" of the code, I find the longest line/instruction, then add 3 spaces, and that's where the ; for comments should "align" to throughout the surrounding code. If there are large amounts of "alignment" changes, then I tend to add an extra newline between "parts" of code, e.g. between two separate .procs, two separate .segments, etc.. You can see an example of this in resetstub vs. reset_fastrom, and the extra newline before .segment CODE7. In fact, adding some spacing (newlines) before a new .segment is very useful (to me) -- otherwise it's very easy to "forget" what segment you're working in.

3. Changed rep #$38 into rep #$30/cld, and moved the cld up to inside of resetstub. This makes it clearer what's going on within reset_fastrom. Also added comment explaining what cld does.

4. I documented what exactly rep #$30 did to A and X/Y sizes. You can ABSOLUTELY remove this comment if needed; I've gotten in the habit of doing this in the past few weeks due to Espozo, so it's a habit at this point. If I was writing my own code I wouldn't comment it.

5. Moved comment about "temporarily move direct page" to the actual tcd statement, since for your ldx #LAST_STACK_ADDR/txs example above, you had the equivalent of that type of command on the txs line, not the ldx line. I did the same for the lda #ZEROPAGE_BASE/tcd line. It's important to be consistent about your comments and where you put them.

I hope my comments (pun intended) are taken as constructive.
Re: A simple sprite demo for teaching
by on (#140733)
koitsu wrote:
ROMSPEED_200NS (not ROMSPEED_120NS, which I think you'd need to fix)

I've since moved the segments to $808000, etc.
Code:
  # The fast ROM area starts at $808000.
  # It's mirrored into the slow ROM area.
  ROM0:       start =  $808000, size = $8000, fill = yes;
  ROM1:       start =  $818000, size = $8000, fill = yes;


I've also added a snippet to the end of the init code that sets the ROM access speed as specified in the ROM header.
Code:
; in snesheader.s
.export map_mode:far
; ...
map_mode:
  .byte MAPPER_LOROM|ROMSPEED_200NS

; in init.s
MEMSEL = $80420D  ; S-CPU I/O: bit 0 enables fast ROM above $808000
; inside reset_fastrom
  lda map_mode
  and #$10
  lsr a
  lsr a
  lsr a
  lsr a
  sta MEMSEL


Quote:
I thought you and thefox said that it was possible to get the address of a MEMORY or SEGMENTS label within code itself? If that's the case, then the STACK_BASE/STACK_SIZE/LAST_STACK_ADDR and ZEROPAGE_BASE stuff shouldn't exist

Direct page I can do.
Code:
.import __ZEROPAGE_RUN__

; Mask off low byte to allow use of $000000-$00000F as local variables
ZEROPAGE_BASE = __ZEROPAGE_RUN__ & $FF00


There are two good ways to reserve space for the stack: as a constant address outside any defined MEMORY area or as the top of a .RES inside .segment "BSS". The program is currently using the former method. To use the latter, I could start BSS at $0100 and have this in init:
Code:
.segment "BSS"
stack: .res $100
stack_end:

; Then at the top of init_fastrom:
  ldx #stack_end-1
  txs

Would this be a better way?

Quote:
Spacing of equate/= assignments. These should line up, making it visually easy for the person to know what's what. You (to date) are the only 65xx programmer I've met who doesn't "align" his formatting.

Apparently other people disagree with you and prefer not lining up the equal signs in a pseudo-tabular form. See Code formatting: is lining up similar lines ok?
The justification for this is that changes to one line often require every line to be edited. That makes it more effort to change, and harder to understand diffs.


koitsu wrote:
if it was from the start I would be screaming loudly over "just two spaces then a semicolon, who cares". It really doesn't flow well.

I guess failure to horizontally align comments is another habit I picked up from C and other high-level languages, where the length of a line of code varies considerably. Even in assembly language, longer expressions such as several constants OR'd together may cause a statement to exceed 24 columns, such as .byte MAPPER_LOROM|ROMSPEED_200NS. An editor can be set to make everything after an unquoted ; character pink anyway.
Re: A simple sprite demo for teaching
by on (#140971)
Is this an improvement at all?
Re: A simple sprite demo for teaching
by on (#140983)
Quote:
The justification for this is that changes to one line often require every line to be edited. That makes it more effort to change, and harder to understand diffs.
However, in a tutorial, accessibility ideally trumps all, including creator time. And you probably aren't going to be altering line width THAT much.

Should the programming practices discussion get its own threadsplit? I still think rolled loops are more readable.

koitsu wrote:
3. Changed rep #$38 into rep #$30/cld, and moved the cld up to inside of resetstub. This makes it clearer what's going on within reset_fastrom. Also added comment explaining what cld does.
I'd use REP #$38 and point out the equivalence in a comment, were I writing it.
Re: A simple sprite demo for teaching
by on (#141134)
I've released version 0.05 incorporating improvements based on the suggestions made by koitsu and Espozo.
Re: A simple sprite demo for teaching
by on (#183059)
And what was going to be 0.06 until Haunted: Halloween '85 and The Curse of Possum Hollow happened is now on Git:
pinobatch/lorom-template.

Image
Git gud