It is currently Thu Dec 14, 2017 7:50 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 9 posts ] 
Author Message
PostPosted: Fri Mar 28, 2014 12:52 pm 
Offline
Formerly ~J-@D!~
User avatar

Joined: Sun Mar 12, 2006 12:36 am
Posts: 445
Location: Rive nord de Montréal
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:
    ; 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 90 times
Top
 Profile  
 
PostPosted: Tue Apr 01, 2014 3:15 pm 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
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:
.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:
.ifndef NES_HEADER_INC
NES_HEADER_INC = 1

[snip]

.endif ;NES_HEADER_INC


Top
 Profile  
 
PostPosted: Tue Apr 01, 2014 4:25 pm 
Offline
NESICIDE developer
User avatar

Joined: Mon Oct 13, 2008 7:55 pm
Posts: 1058
Location: Minneapolis, MN
Jarhmander, this would make a really nice set of templates for NESICIDE projects. :beer:


Top
 Profile  
 
PostPosted: Wed Apr 02, 2014 10:34 am 
Offline
Formerly ~J-@D!~
User avatar

Joined: Sun Mar 12, 2006 12:36 am
Posts: 445
Location: Rive nord de Montréal
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:
.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:
.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.


Top
 Profile  
 
PostPosted: Wed Apr 02, 2014 11:53 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2983
Location: Tampere, Finland
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: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Wed Apr 02, 2014 2:06 pm 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
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.)


Top
 Profile  
 
PostPosted: Fri Apr 04, 2014 6:30 am 
Offline
Formerly ~J-@D!~
User avatar

Joined: Sun Mar 12, 2006 12:36 am
Posts: 445
Location: Rive nord de Montréal
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.


Top
 Profile  
 
PostPosted: Thu Apr 24, 2014 12:06 pm 
Offline

Joined: Tue Jul 12, 2011 10:58 am
Posts: 265
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 :)


Top
 Profile  
 
PostPosted: Thu Apr 24, 2014 8:21 pm 
Offline
NESICIDE developer
User avatar

Joined: Mon Oct 13, 2008 7:55 pm
Posts: 1058
Location: Minneapolis, MN
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group