1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-04-30 07:37:19 +00:00

NES mapper experiments

This commit is contained in:
Karol Stasiak 2018-03-17 17:09:30 +01:00
parent fa34402749
commit fcf4af6c55
8 changed files with 391 additions and 8 deletions

View File

@ -33,21 +33,33 @@ The minimal Famicom program thus looks like this:
To use a mapper of your choice, create a new `.ini` file with the definitions you need. To use a mapper of your choice, create a new `.ini` file with the definitions you need.
The most important ones are `[output]format` and `[allocation]segments`. The most important ones are `[output]format` and `[allocation]segments`.
Currently, its a bit inconvenient to create programs using mappers that change the bank containing the interrupt vectors.
Therefore, it's recommended to stick to mappers that have a fixed bank at the end of the address space.
Mappers that should be fine: NROM (0), CNROM (1), UxROM(2), MMC2 (9), MMC3 (4), MMC4 (10), MMC6 (4).
Mappers that can have arbitrary bank at the end and are therefore not recommended: MMC1 (1), MMC5 (5).
You should define at least three segments: You should define at least three segments:
* `default` from $200 to $7FF, it will represent the physical RAM of the console. * `default` from $200 to $7FF, it will represent the physical RAM of the console.
* `chrrom` (sample name) from $0000 to $3FFF, it will represent the CHRROM (if you need more). * `chrrom` (sample name) from $0000 to $1FFF, it will represent the CHRROM
(if you need more, you can make it bigger, up to $ffff, or even add another segment of CHRROM).
Put there only arrays with pattern tables. Don't read from them directly, it won't work.
* `prgrom` (sample name) from either $8000 or $C000 to $FFFF, it will contain the code of your program. * `prgrom` (sample name) it will contain the code of your program and read-only data.
Each segment should be defined in a range it is going to be switched into.
You should set the `default_code_segment` to the segment that contains the $FFxx addresses. You should set the `default_code_segment` to the segment that contains the $FFxx addresses.
If your mapper supports it, you can add more CHRROM or PRGROM segments, If your mapper supports it, you can add more CHRROM or PRGROM segments,
just specify them correctly in the `[output]format` tag. just specify them correctly in the `[output]format` tag.
The `[output]format` tag should contain a valid iNES header of the mapper of your choice and then all the segments in proper order. The `[output]format` tag should contain a valid iNES or NES 2.0 header of the mapper of your choice
and then all the segments in proper order (first PRGROM, then CHRROM).
See [the MMC4 example](../../include/nes_mmc4.ini) to see how it can be done.
See [the NesDev wiki](https://wiki.nesdev.com/w/index.php/INES) for more info about the iNES file format. See [the NesDev wiki](https://wiki.nesdev.com/w/index.php/NES_2.0) for more info about the NES 2.0 file format.

View File

@ -27,7 +27,9 @@ The following platforms are currently supported:
* `pet` Commodore PET * `pet` Commodore PET
* `nes_small` a tiny 32K PRGROM + 8K CHRROM Famicom/NES program * `nes_small` a tiny 32K PRGROM + 8K CHRROM Famicom/NES program, using iNES mapper 0 (NROM)
* `nes_mcc4` a 128K PRGROM + 128K CHRROM + extra 8KRAM Famicom/NES program, using iNES mapper 10 (MMC4)
For more complex programs, you need to create your own "platform" definition. For more complex programs, you need to create your own "platform" definition.
Read [the NES programming guide](./famicom-programming-guide.md) for more info. Read [the NES programming guide](./famicom-programming-guide.md) for more info.

View File

@ -28,6 +28,8 @@
* [NES 101 tutorial example](nes/nestest.mfk) a port of the tutorial example from the NES 101 tutorial by Michael Martin * [NES 101 tutorial example](nes/nestest.mfk) a port of the tutorial example from the NES 101 tutorial by Michael Martin
* [MMC4 example](nes/nestest_mmc4.mfk) the same thing as above, but uses a MMC4 mapper just to test bankswitching
## Apple II examples ## Apple II examples
* [Bell](apple2/bell.mfk) a program that goes \*ding!\* * [Bell](apple2/bell.mfk) a program that goes \*ding!\*

View File

@ -1,4 +1,5 @@
// Based upon the example code from NES 101 tutorial by Michael Martin // Based upon the example code from NES 101 tutorial by Michael Martin
// compile with -t nes_small
void main() { void main() {
init_graphics() init_graphics()

View File

@ -0,0 +1,269 @@
// Based upon the example code from NES 101 tutorial by Michael Martin
// compile with -t nes_mmc4
void main() {
init_graphics()
init_input()
init_sound()
ppu_ctrl = %10001000
ppu_mask = %00011110
while(true){}
}
void nmi() {
set_prg_bank(1)
scroll_screen()
set_prg_bank(2)
update_sprite()
set_prg_bank(3)
react_to_input()
}
void irq() {
}
array oam_buffer [256] @$200
byte scroll
byte a
segment(ram) sbyte dx
void init_graphics() {
set_chr_bank0($06)
set_chr_bank1($1A)
init_sprites()
set_prg_bank(6)
load_palette()
load_name_tables()
init_scrolling()
}
void init_input() {
a = 0
}
void init_sound() {
apu_status = 1
apu_pulse1_sweep = 0
apu_frame_counter = $40
}
void init_sprites() {
byte i
for i,255,downto,0 {
oam_buffer[i] = 0
}
oam_buffer[0] = $70
oam_buffer[1] = 1
oam_buffer[3] = 1
dx = 1
}
segment(prgrom6)
noinline void load_palette() {
byte i
ppu_set_addr($3f00)
for i,0,until,palette.length {
ppu_write_data(palette[i])
}
}
segment(prgrom6)
noinline void load_name_tables() {
pointer p
byte page_count
byte b
read_ppu_status()
p = bg.addr
ppu_set_addr($2400)
for page_count,0,until,4 {
for b,0,until,255 {
ppu_write_data(p[b])
}
p.hi += 1
}
for page_count,0,until,4 {
for b,0,until,255 {
ppu_write_data(0)
}
}
}
void init_scrolling() {
scroll = 240
}
segment(prgrom2)
noinline void update_sprite() {
ppu_oam_dma_write(oam_buffer.addr.hi)
if oam_buffer[3] == 0 {
dx = 1
high_c()
} else if oam_buffer[3] == 255-8 {
dx = -1
high_c()
}
oam_buffer[3] += dx
}
segment(prgrom3)
noinline void react_to_input() {
strobe_joypad()
if read_joypad1() & 1 != 0 { // A button
reverse_dx()
}
read_joypad1() // B button
read_joypad1() // Select button
read_joypad1() // Start button
if read_joypad1() & 1 != 0 { // Up button
if oam_buffer[0] > 7 { oam_buffer[0] -= 1}
}
if read_joypad1() & 1 != 0 { // Down button
if oam_buffer[0] < 223 { oam_buffer[0] += 1}
}
}
segment(prgrom3)
void reverse_dx() {
dx = 0 - dx
low_c()
}
segment(prgrom3)
void low_c() {
apu_pulse1_ctrl = $84
apu_pulse1_period = $9AA
}
segment(prgrom2)
void high_c() {
apu_pulse1_ctrl = $86
apu_pulse1_period = $869
}
segment(prgrom1)
noinline void scroll_screen() {
ppu_write_data(0)
if scroll != 0 {
scroll -= 1
ppu_set_scroll(0, scroll)
}
}
array bg = [
" " ascii,
"12345678901234567890123456789012" ascii,
" " ascii,
" " ascii,
" PRESENTING NES 101 " ascii,
" A GUIDE FOR OTHERWISE " ascii,
" EXPERIENCED PROGRAMMERS " ascii,
" " ascii,
" TUTORIAL FILE BY " ascii,
" MICHAEL MARTIN " ascii,
" " ascii,
" " ascii,
" " ascii,
" PRESS UP AND DOWN TO SHIFT " ascii,
" THE SPRITE " ascii,
" " ascii,
" PRESS A TO REVERSE DIRECTION" ascii,
" " ascii,
" " ascii,
" " ascii,
" " ascii,
" " ascii,
" " ascii,
"CHARACTER SET HIJACKED FROM " ascii,
"COMMODORE BUSINESS MACHINES " ascii,
" (C64'S CHARACTER ROM)" ascii,
" " ascii,
"READY. " ascii,
" " ascii,
" " ascii,
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
$00,$00,$00,$00,$00,$00,$00,$00,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F
]
array palette = [
$0E,$00,$0E,$19,$00,$00,$00,$00,$00,$00,$00,$00,$01,$00,$01,$21,
$0E,$20,$22,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
]
segment(chrrom0)
array charset @$6200 = [
$00,$00,$00,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$18,$18,$18,$00,$00,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$FF,$66,$FF,$66,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$3E,$60,$3C,$06,$7C,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$62,$66,$0C,$18,$30,$66,$46,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$3C,$38,$67,$66,$3F,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$06,$0C,$18,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$0C,$18,$30,$30,$30,$18,$0C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$30,$18,$0C,$0C,$0C,$18,$30,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$66,$3C,$FF,$3C,$66,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$18,$18,$7E,$18,$18,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$00,$00,$00,$18,$18,$30,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$00,$7E,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$00,$00,$00,$18,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$03,$06,$0C,$18,$30,$60,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$6E,$76,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$18,$38,$18,$18,$18,$7E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$06,$0C,$30,$60,$7E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$06,$1C,$06,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$06,$0E,$1E,$66,$7F,$06,$06,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$60,$7C,$06,$06,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$60,$7C,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$66,$0C,$18,$18,$18,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$66,$3C,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$66,$3E,$06,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$18,$00,$00,$18,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$18,$00,$00,$18,$18,$30,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$0E,$18,$30,$60,$30,$18,$0E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$00,$7E,$00,$7E,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$70,$18,$0C,$06,$0C,$18,$70,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$06,$0C,$18,$00,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$6E,$6E,$60,$62,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$18,$3C,$66,$7E,$66,$66,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7C,$66,$66,$7C,$66,$66,$7C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$60,$60,$60,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$78,$6C,$66,$66,$66,$6C,$78,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$60,$60,$78,$60,$60,$7E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$60,$60,$78,$60,$60,$60,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$60,$6E,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$7E,$66,$66,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$18,$18,$18,$18,$18,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$1E,$0C,$0C,$0C,$0C,$6C,$38,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$6C,$78,$70,$78,$6C,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$60,$60,$60,$60,$60,$60,$7E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$63,$77,$7F,$6B,$63,$63,$63,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$76,$7E,$7E,$6E,$66,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$66,$66,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7C,$66,$66,$7C,$60,$60,$60,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$66,$66,$66,$3C,$0E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7C,$66,$66,$7C,$78,$6C,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$66,$60,$3C,$06,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$18,$18,$18,$18,$18,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$66,$66,$66,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$66,$66,$3C,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$63,$63,$63,$6B,$7F,$77,$63,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$3C,$18,$3C,$66,$66,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$66,$66,$66,$3C,$18,$18,$18,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$7E,$06,$0C,$18,$30,$60,$7E,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$30,$30,$30,$30,$30,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$0C,$12,$30,$7C,$30,$62,$FC,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$3C,$0C,$0C,$0C,$0C,$0C,$3C,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$18,$3C,$7E,$18,$18,$18,$18,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$00,$10,$30,$7F,$7F,$30,$10,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
]
segment(chrrom1)
array sprites @$A000 = [
$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,
$18,$24,$66,$99,$99,$66,$24,$18,$00,$18,$18,$66,$66,$18,$18,$00
]

64
include/nes_mmc4.ini Normal file
View File

@ -0,0 +1,64 @@
; a NES MMC4 cartridge, uses iNES mapper 10
; 8×16 kB PRGROM and 32×4kB CHRROM
; $c000$ffff is assumed to be fixed to prgrom7, $8000-$bfff can be switched
; uses horizontal mirroring:
; - to use vertical mirroring, change byte #6 of the header from $A0 to $A1
; uses extra 8K of RAM at $6000$7fff that is not battery-backed:
; - to disable it, change byte #10 of the header from $07 to 0 and remove the ram segment
; - to make it battery-backed, change byte #10 of the header from $07 to $77
; output file size: 262160 bytes
[compilation]
arch=ricoh
modules=nes_hardware,nes_routines,default_panic,nes_mmc4
ro_arrays=true
[allocation]
zp_pointers=all
segments=default,ram,prgrom0,prgrom1,prgrom2,prgrom3,prgrom4,prgrom5,prgrom6,prgrom7,chrrom0,chrrom1
default_code_segment=prgrom7
segment_default_start=$200
segment_default_end=$7ff
segment_ram_start=$6000
segment_ram_end=$7fff
segment_prgrom7_start=$c000
segment_prgrom7_end=$ffff
segment_prgrom0_start=$8000
segment_prgrom0_end=$bfff
segment_prgrom1_start=$8000
segment_prgrom1_end=$bfff
segment_prgrom2_start=$8000
segment_prgrom2_end=$bfff
segment_prgrom3_start=$8000
segment_prgrom3_end=$bfff
segment_prgrom4_start=$8000
segment_prgrom4_end=$bfff
segment_prgrom5_start=$8000
segment_prgrom5_end=$bfff
segment_prgrom6_start=$8000
segment_prgrom6_end=$bfff
segment_chrrom0_start=$0000
segment_chrrom0_end=$ffff
segment_chrrom1_start=$0000
segment_chrrom1_end=$ffff
[output]
style=single
format=$4E,$45,$53,$1A, 8,16,$A0,8, 0,0,$07,0, 2,0,0,0, prgrom0:$8000:$bfff,prgrom1:$8000:$bfff,prgrom2:$8000:$bfff,prgrom3:$8000:$bfff,prgrom4:$8000:$bfff,prgrom5:$8000:$bfff,prgrom6:$8000:$bfff,prgrom7:$c000:$ffff,chrrom0:$0000:$ffff,chrrom1:$0000:$ffff
extension=nes

32
include/nes_mmc4.mfk Normal file
View File

@ -0,0 +1,32 @@
asm inline void set_prg_bank(byte a) {
STA $A000
? RTS
}
// calling with with e.g. $1E will set $0000-$0fff to $E000-$EFFF in chrrom1
asm inline void set_chr_bank0(byte a) {
STA $B000
STA $C000
? RTS
}
// calling with with e.g. $1E will set $1000-$1fff to $E000-$EFFF in chrrom1
asm inline void set_chr_bank1(byte a) {
STA $D000
STA $E000
? RTS
}
// TODO: FD/FE latches
asm inline void set_vertical_mirroring() {
LDA #0
STA $F000
? RTS
}
asm inline void set_horizontal_mirroring() {
LDA #1
STA $F000
? RTS
}

View File

@ -1,7 +1,8 @@
; a very simple NES cartridge format ; a very simple NES cartridge format
; uses mapper 0 and no bankswitching, so it's only good for very simple games ; uses mapper 0 and no bankswitching, so it's only good for very simple games
; assumes CHRROM is at chrrom:$0000-$3fff and PRGROM is at prgrom:$8000-$ffff ; assumes CHRROM is at chrrom:$0000-$1fff and PRGROM is at prgrom:$8000-$ffff
; uses horizontal mirroring; to use vertical mirroring, change byte #6 of the header from 0 to 1 ; uses horizontal mirroring; to use vertical mirroring, change byte #6 of the header from 0 to 1
; output file size: 40976 bytes
[compilation] [compilation]
arch=ricoh arch=ricoh
@ -21,11 +22,11 @@ segment_prgrom_start=$8000
segment_prgrom_end=$ffff segment_prgrom_end=$ffff
segment_chrrom_start=$0000 segment_chrrom_start=$0000
segment_chrrom_end=$3fff segment_chrrom_end=$1fff
[output] [output]
style=single style=single
format=$4E,$45,$53,$1A, 2,2,0,0, 0,0,0,0, 0,0,0,0, prgrom:$8000:$ffff, chrrom:$0000:$3fff format=$4E,$45,$53,$1A, 2,1,0,0, 0,0,0,0, 0,0,0,0, prgrom:$8000:$ffff, chrrom:$0000:$1fff
extension=nes extension=nes