NESdev-kit for the cc65 suite

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

Post Reply
User avatar
Jarhmander
Formerly ~J-@D!~
Posts: 569
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

NESdev-kit for the cc65 suite

Post by Jarhmander »

Hello homebrewers. I began working on something, and I thought I should share it at this point to see if there's any interest on that kind of project.

Please read, don't tl;dr and run the 'out.nes' file; it's possibly the most boring NES file you'll see in your whole life, it just cycles the background color slowly.

Basically I wanted to play with mapper 69 (FME-7) using ca65/ld65 and I wanted to do things clean: configuring the NES header, managing PRG-ROM banks, etc. Now I wrote a set of assembly and include files along with a linker script, and I thought of a "system" that could hopefully apply to other mappers. This "system" is just a bunch of files that one would use to NESdev quickly on a particular mapper. It describes the mapper and allow easy configuration of the NES header and code/data placement in segments (the segments are PRG banks); any data placed in a segment can be easily accessed without knowing exactly where it is located.

So, now the NESdev-kit consists of:
  • nes-header.s/.inc : these files describe a NES v1 header. These files does not need to be edited;
  • configure.inc : contains macros to configure the NES header. One would just have to 'call' them to configure the NES file;
  • core.s/.inc : core definitions, it declares the 6502 vectors and other 'core' things;
  • mapperN.s/.inc (not currently in the zip file) : macros and routines to control mapper N's registers.
  • mapperN.ld : Linker script for mapper N. Unfortunately, in most cases this file would need to be edited in a project, but the edits would be minimal and located at one particular place.
In core.s/.inc in the zip file, I have defined some "registers" in zeropage. It is not part of the NESdev-kit project, it's just there for convenience as I plan to use some zeropage addresses as general purpose registers; just ignore them.

configure.inc currently contains these 2 macros:
  • nesconfig mirroring, mapper, batterybackupsram
    configure mirroring, mapper and whether there is battery backed SRAM. The last parameter is optional and defaults to 'no'. Choosing a mapper with this macro just merely verify that the appropriate linker script is used to link the NES program, an assert fires if the mapper parameter don't match with the linker script;
  • nessize nprgrombank_16K, nchrbank_8K, nprgrambank_8K
    configure PRG-ROM, CHR-ROM/RAM and PRG-RAM size. The last two parameters default to 0.
For now I don't even configure some other settings like PAL/NTSC and PlayChoice bits but this could be added.

Now for the bank mapping thing:
If you look at the linker script (it's pretty big and ugly, but it does the job) you'll find at the SEGMENT section, a bunch of PRGBK** segment definitions. It's suited for FME-7. One can configure the runtime address for a segment (bank) really quick: change the x in "run = ROMSByyATx000" to 6/8/A/C, and you basically changed the runtime address of the PRG bank yy.

Now look at foo.s in the zip file, the code is not so good but it illustrates that code in other segments is easily accessible. The nmi basically calls some_routine which is located in another bank. The nice thing is, the following code will map the bank where some_routime is located at the runtime address it expects to be, and successfully calls it:

Code: Select all

    ; FME-7: select bank of some_routine where it expects to be run.
    ldy #(8 + >.bank(some_routine))
    lda #<.bank(some_routine)
    sty $8000
    sta $A000

    jsr some_routine
That's because I (ab)used the bank attribute of memory regions: basically, the low byte is the bank number (0-63) and the high byte is the runtime address for the bank (where the bank will be mapped)—this would me abstracted into a macro. The astute reader will find that if the runtime address is $6000, it will write at register 8, the code just ignores the upper bits (PRG-RAM CE and PRG-RAM/ROM select) and happens to work anyways. As nice as it looks, it's not perfect because there's a pitfall: if you use ex. segment "PRGBK07", then the preceding segments should have at least some data in them (i.e. be not empty), because otherwise, as empty segments does not take space, the banks will be "shifted" down to lower addresses, and the bank logic will fail miserably. That kind of problem could be easily troubleshooted by looking at the map file however. Now, I know that one could just change the upper three bits of an absolute symbol (in a jsr, lda, etc) according to the expected runtime address each time it is used, but because the 6502 isn't much suitable for doing position-independent code, it's simpler having each bank run at a particular address, and if the linker can do the relocation for you, why not using it to simplify writing code?

Now for the NESdev-kit project, it would simply involve having other mappers defined and refine the kit, as I surely have overlooked some things. There are some easy mappers, but some (MMC3 and particularly MMC5) will cause trouble. That's why I want to bring your lights here. The goal would be to make "The NESdev-kit" that would allow novice programmers to easily start a NESdev project with a mapper of his choice, it should be as user-friendly as it is possible by using only the cc65 toolsuite. If it becomes mature enough, a simple "wizard" in NESICIDE would setup automatically all the rest that can't just be done with cc65 et al. While I only consider assembly, I think it might be doable to somewhat support some banking features in C but I haven't looked into it at all; if it's possible to do it, it would be even nicer because it would lower even more the "entry-level" for programming a NES game—well, a big one, as Shiru insisted on the fact that the biggest problem with C in cc65 was code size.

Any relevant suggestion, criticism, is more than welcome.
Attachments
nesdev-kit.zip
(7.72 KiB) Downloaded 197 times
((λ (x) (x x)) (λ (x) (x x)))
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

Re: NESdev-kit for the cc65 suite

Post by Movax12 »

I have been working something like this. Example: Say I have a macro called setDataBank. That macro outputs different code for different supported mappers. The mapper for the project is set in a configuration file. More mapper support is like writing a device driver I suppose. If various mappers are supported properly and there are no exclusive features needed from a specific mapper, you could build for different mapper/boards with a simple configuration change. I have ca65 actually creating my memory configuration file via std out, filtered through a small perl script, since I don't know a better/quicker way to achieve the flexibility I desire.

I wanted to point out something as well:

Code: Select all

.ifndef NES_HEADER_INC
.define NES_HEADER_INC

[snip]

.endif ;NES_HEADER_INC
This won't do the job, since the .define style macro will always be replaced by its value except when using .undefine
So in this case, if your constant is defined on the second include of this code, NES_HEADER_INC will be blank. (Since you defined it to be blank.) You need to do:

Code: Select all

.ifndef NES_HEADER_INC
NES_HEADER_INC = 1

[snip]

.endif ;NES_HEADER_INC
User avatar
cpow
NESICIDE developer
Posts: 1097
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: NESdev-kit for the cc65 suite

Post by cpow »

Jarhmander, this would make a really nice set of templates for NESICIDE projects. :beer:
User avatar
Jarhmander
Formerly ~J-@D!~
Posts: 569
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: NESdev-kit for the cc65 suite

Post by Jarhmander »

Movax12 wrote: I wanted to point out something as well:

[snip]

This won't do the job, since the .define style macro will always be replaced by its value except when using .undefine
Yeah I figured that out two days ago when I 'recursively' included some header file. After seeing errors, I read the docs and saw that .defined et al work with symbols, not macros, so I did pretty much what you wrote.

Also, what you described is interesting, can you show more about what you did? That's why I started this thread: to see what other have done and possibly merge the better of all solutions and make something awesome. It may end up like the 6502-style SPC700 syntax collab thing and go nowhere, but let's try first.



I continued working on this, adding support for constructors (in the module sense, using the .constructor directive), adding pseudo-ops and safer bankswitching.

This is the swbankprg bankswitching macro (along with an helper macro that shouldn't be used by user code), it only deals with PRG-ROM switching.

Code: Select all

.macro swbankprg_check addr
    .assert >.bank(*) <> >.bank(addr) .or .bank(*) = .bank(addr), lderror, .sprintf("swbank: switching '%s' into its runtime address would switch into current runtime address", .string(addr))
    .assert .bank(addr) <> $FFFF, lderror, .sprintf("swbank: symbol '%s' is located in the fixed bank; you cannot bankswitch the fixed bank", .string(addr))
.endmacro

.macro swbankprg addr
    swbankprg_check addr

    lda #(8 + >.bank(addr))
    sta mapper_cmdreg
    sta $8000

    lda #<.bank(addr)
    sta $A000
.endmacro
Basically it's the same trick as before, just with preliminary checks. As the text says, it prevents switching the fixed bank anywhere (I see no interest switching the last bank to somewhere else) and checks if switching the bank would replace the current running bank with another bank—you usually want to avoid, say, bankswitching in $8000 while you're running code in $8000-$9FFF. The linker script was modified to give a bank attribute to the fixed bank ($FFFF, a sentinel value) so the macro above works. I have another macro, swbankprg_nosave, that does the same thing but without writing to mapper_cmdreg. With the assumption that NMIs doesn't preempt IRQs, IRQs are not enabled in NMI and code prevent reentring NMIs, then code running from reset is preempted by either IRQs or NMIs, so that code uses swbankprg, and IRQs or NMIs use swbankprg_nosave; if IRQs or NMIs use that macro, they just have to restore mapper register $8000 when they finish doing mapper register writes. This may be sufficient for IRQs, as it would only play with the timer IRQ and (possibly) CHR banks, but NMI code may want to switch PRG-ROM (ex. for music and FX engine, at the end of PPU updates), so one should still track which bank is visible at $6000, $8000, $A000 and $C000 by using other variables.

I added another file that verify that banks are correctly set up in memory:

Code: Select all

.repeat 64,I
.import .ident(.sprintf("__PRGBK%02d_LOAD__",I))
.import .ident(.sprintf("__PRGBK%02d_SIZE__",I))

    .segment .sprintf("PRGBK%02d",I)

.assert .ident(.sprintf("__PRGBK%02d_SIZE__",I)) = 0 .or .ident(.sprintf("__PRGBK%02d_LOAD__",I)) = (I << 13), lderror, .sprintf("checkbanks: bank %d expected at 0x%08X", I, (I << 13))

.endrepeat
Now the problem is that it shows you if you screwed up and which bank is displaced, but not where it ended up. That's because .sprintf expects constant expressions and such linker symbols are not constant expressions, so I cannot show their values. Enhancements are welcome.
cpow wrote:Jarhmander, this would make a really nice set of templates for NESICIDE projects. :beer:
Yeah, that's the goal a bit. Working in an IDE helps the novice NES programmer—and the non-novice too :)— and with a set of template that works essentially the same, it would require less initial user setup and less rewrite when changing mappers.
((λ (x) (x x)) (λ (x) (x x)))
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: NESdev-kit for the cc65 suite

Post by thefox »

Some nice tricks there. I especially liked how you exploited the fact that the MEMORY configuration's bank number can be used to store any 32 bits of data.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

Re: NESdev-kit for the cc65 suite

Post by Movax12 »

Jarhmander wrote: Also, what you described is interesting, can you show more about what you did?
I'll share what I have when I finish cleaning it up a bit - not sure when that is. I am especially not happy with my function calling code, but I think I have that moving along nicely now, with some ideas from thefox. Basically though I wanted to be able to write text files with ca65, so I pipe all output to a Perl script that looks for WRITEFILE and CLOSEFILE or some such.
It allows for things like writing the linker config file with ca65, and my idea for memory management across modules. ( It helps with accessing things you can't get at assemble from the linker.)
User avatar
Jarhmander
Formerly ~J-@D!~
Posts: 569
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: NESdev-kit for the cc65 suite

Post by Jarhmander »

Interesting idea, at least you can still use some symbols at compile time and not repeat yourself, as the linker script is generated from your sources.

Except if you want to keep it privately, please share even if it's unfinished/unpolished—I mean, the "kit" I shared is by no means complete, I'll refine it with usage and suggestions. You would just have to mention what parts of your thing need more work or have issues, so people won't lose their time pointing out flaws.
((λ (x) (x x)) (λ (x) (x x)))
slobu
Posts: 276
Joined: Tue Jul 12, 2011 10:58 am

Re: NESdev-kit for the cc65 suite

Post by slobu »

cpow wrote:Jarhmander, this would make a really nice set of templates for NESICIDE projects. :beer:
I second that! The point where *I* failed is trying to understand the mapper/settings files for NESICIDE. It sounds like another example template using your work would give me another way to compare and contrast.

NOTE: I really mean me myself and I failing. I haven't even asked for help yet :)
User avatar
cpow
NESICIDE developer
Posts: 1097
Joined: Mon Oct 13, 2008 7:55 pm
Location: Minneapolis, MN
Contact:

Re: NESdev-kit for the cc65 suite

Post by cpow »

slobu wrote:trying to understand the mapper/settings files for NESICIDE.
...there aren't any 'settings' per se. The CC65 linker configuration file is what you need, just like if you're working with CC65 directly. There is a MMC1 example project based on one of Tepples' examples. At some point I planned to turn example projects into templates. NESICIDE already supports creating a project using an existing template, so that part is now done. Just need to take the time to import the templates. But in reality me doing that isn't much different than you opening up the example project and modifying it.

I had also had the thought to support creation of 'library projects' wherein a library could provide the necessary mapper-specific stuff. Haven't got around to that yet though.
Post Reply