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:
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:
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:
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.
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
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.