Progress Thread - Project Blue
Moderator: Moderators
- toggle switch
- Posts: 139
- Joined: Fri Sep 30, 2016 8:57 pm
Re: Progress Thread - Project Blue
honestly i'll probably need some help moving to another mapper.
is there a document somewhere that covers linker files and how they work? or a sample GTROM project in NESICIDE? i can't seem to find any relevant pages on how this stuff works and how to set it up.
is there a document somewhere that covers linker files and how they work? or a sample GTROM project in NESICIDE? i can't seem to find any relevant pages on how this stuff works and how to set it up.
Re: Progress Thread - Project Blue
The official documentation of linker configuration files is ld65 Users Guide. You'll need a MEMORY area for each 32K bank and probably for the pseudo-fixed bank. If you want, I could make a BNROM version of my SNROM/UNROM template. This should help because GTROM is the same as BNROM except for the port moved down to $5000 and bank switching of CHR RAM and nametable memory.
Re: Progress Thread - Project Blue
I setup a gtrom configuration for ca65 last week. You also need to do some trickery to setup the fixed segments. I can post that too if you want.
Code: Select all
MEMORY {
ZP: start = $00, size = $100, type = rw;
HEADER: start = 0, size = $0010, type = ro, file = %O, fill=yes, fillval=$00;
RAM: start = $0300, size = $0500, type = rw;
PRG0: start = $8000, size = $8000, file = %O, fill = yes, fillval = $0, bank=0;
PRG1: start = $8000, size = $8000, file = %O, fill = yes, fillval = $1, bank=1;
PRG2: start = $8000, size = $8000, file = %O, fill = yes, fillval = $2, bank=2;
PRG3: start = $8000, size = $8000, file = %O, fill = yes, fillval = $3, bank=3;
PRG4: start = $8000, size = $8000, file = %O, fill = yes, fillval = $4, bank=4;
PRG5: start = $8000, size = $8000, file = %O, fill = yes, fillval = $5, bank=5;
PRG6: start = $8000, size = $8000, file = %O, fill = yes, fillval = $6, bank=6;
PRG7: start = $8000, size = $8000, file = %O, fill = yes, fillval = $7, bank=7;
PRG8: start = $8000, size = $8000, file = %O, fill = yes, fillval = $8, bank=8;
PRG9: start = $8000, size = $8000, file = %O, fill = yes, fillval = $9, bank=9;
PRG10: start = $8000, size = $8000, file = %O, fill = yes, fillval = $A, bank=10;
PRG11: start = $8000, size = $8000, file = %O, fill = yes, fillval = $B, bank=11;
PRG12: start = $8000, size = $8000, file = %O, fill = yes, fillval = $C, bank=12;
PRG13: start = $8000, size = $8000, file = %O, fill = yes, fillval = $D, bank=13;
PRG14: start = $8000, size = $8000, file = %O, fill = yes, fillval = $E, bank=14;
PRG15: start = $8000, size = $8000, file = %O, fill = yes, fillval = $F, bank=15;
}
SEGMENTS {
INESHDR: load = HEADER, type = ro, align = $10;
ZEROPAGE: load = ZP, type = zp;
BSS: load = RAM, type = bss, define = yes, align = $100;
FIXED0: load = PRG0, type = ro, align = $100;
B0: load = PRG0, type = ro, align = $100;
VECTORS0: load = PRG0, type = ro, start = $FFFA;
FIXED1: load = PRG1, type = ro, align = $100;
B1: load = PRG1, type = ro, align = $100;
VECTORS1: load = PRG1, type = ro, start = $FFFA;
FIXED2: load = PRG2, type = ro, align = $100;
B2: load = PRG2, type = ro, align = $100;
VECTORS2: load = PRG2, type = ro, start = $FFFA;
FIXED3: load = PRG3, type = ro, align = $100;
B3: load = PRG3, type = ro, align = $100;
VECTORS3: load = PRG3, type = ro, start = $FFFA;
FIXED4: load = PRG4, type = ro, align = $100;
B4: load = PRG4, type = ro, align = $100;
VECTORS4: load = PRG4, type = ro, start = $FFFA;
FIXED5: load = PRG5, type = ro, align = $100;
B5: load = PRG5, type = ro, align = $100;
VECTORS5: load = PRG5, type = ro, start = $FFFA;
FIXED6: load = PRG6, type = ro, align = $100;
B6: load = PRG6, type = ro, align = $100;
VECTORS6: load = PRG6, type = ro, start = $FFFA;
FIXED7: load = PRG7, type = ro, align = $100;
B7: load = PRG7, type = ro, align = $100;
VECTORS7: load = PRG7, type = ro, start = $FFFA;
FIXED8: load = PRG8, type = ro, align = $100;
B8: load = PRG8, type = ro, align = $100;
VECTORS8: load = PRG8, type = ro, start = $FFFA;
FIXED9: load = PRG9, type = ro, align = $100;
B9: load = PRG9, type = ro, align = $100;
VECTORS9: load = PRG9, type = ro, start = $FFFA;
FIXED10: load = PRG10, type = ro, align = $100;
B10: load = PRG10, type = ro, align = $100;
VECTORS10: load = PRG10, type = ro, start = $FFFA;
FIXED11: load = PRG11, type = ro, align = $100;
B11: load = PRG11, type = ro, align = $100;
VECTORS11: load = PRG11, type = ro, start = $FFFA;
FIXED12: load = PRG12, type = ro, align = $100;
B12: load = PRG12, type = ro, align = $100;
VECTORS12: load = PRG12, type = ro, start = $FFFA;
FIXED13: load = PRG13, type = ro, align = $100;
B13: load = PRG13, type = ro, align = $100;
VECTORS13: load = PRG13, type = ro, start = $FFFA;
FIXED14: load = PRG14, type = ro, align = $100;
B14: load = PRG14, type = ro, align = $100;
VECTORS14: load = PRG14, type = ro, start = $FFFA;
FIXED15: load = PRG15, type = ro, align = $100;
B15: load = PRG15, type = ro, align = $100;
VECTORS15: load = PRG15, type = ro, start = $FFFA;
}
FILES {
%O: format = bin;
}
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Progress Thread - Project Blue
Ideally you'd want to be able to
1)define your "pseudo fixed bank" as a segment.
2)in source, place whatever needs to be fixed (ie the bare game engine + probably also music driver) in that segment.
3)instruct the linker to paste that segment at the same addr in each bank it needs to be in.
That way the "fixed bank" is always the size of the engine and not a byte more, which leaves optimal and maximized space for level/song data - and any plausible extras; like if there were level-specific functions or tables for example.
1)define your "pseudo fixed bank" as a segment.
2)in source, place whatever needs to be fixed (ie the bare game engine + probably also music driver) in that segment.
3)instruct the linker to paste that segment at the same addr in each bank it needs to be in.
That way the "fixed bank" is always the size of the engine and not a byte more, which leaves optimal and maximized space for level/song data - and any plausible extras; like if there were level-specific functions or tables for example.
Re: Progress Thread - Project Blue
Having the engine in the fixed bank makes the programming easier, but it wastes a lot of space. Each fixed byte takes up 16 bytes in the ROM, which is a lot!
Technically, all you need in the fixed bank is the vectors, their handlers, and a trampoline for bank switches. Also DPCM data, if you're using that. The engine code can be placed in a separate 32k bank, and the music engine + songs can exist in their own 32 bank. I think this is how Lizard does things.
But this is venturing off into a tangent. My apologies.
Technically, all you need in the fixed bank is the vectors, their handlers, and a trampoline for bank switches. Also DPCM data, if you're using that. The engine code can be placed in a separate 32k bank, and the music engine + songs can exist in their own 32 bank. I think this is how Lizard does things.
But this is venturing off into a tangent. My apologies.
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Progress Thread - Project Blue
Might be a tangent for the compo, but it's a very useful discussion for the project, so please don't feel you need to hold back! It's all very informative, so thanks!
Hadn't even considered that. I don't have the source and am not sure i could make sense of it either, but i think we're pretty tight on cycles, NMI-wise especially.
Would trampolines like that be a significant increase in cycles?
Hadn't even considered that. I don't have the source and am not sure i could make sense of it either, but i think we're pretty tight on cycles, NMI-wise especially.
Would trampolines like that be a significant increase in cycles?
Only if you need to paste the engine across all banks. As i see it, you'd only need to do so across in-game engine/level banks. A bank strictly for storing graphics to be loaded in bulk between levels wouldn't need it, and titles, cutscenes and such wouldn't need it either. But yeah, i think we'd be looking at 12-14 bytes per "fixed" byte with this method. Fortunately, there's 512k of them if we go this path. On the other hand, *some* trampoline would need to be written at least for loading stuff at certain points.Each fixed byte takes up 16 bytes in the ROM, which is a lot!
- toggle switch
- Posts: 139
- Joined: Fri Sep 30, 2016 8:57 pm
Re: Progress Thread - Project Blue
honestly, the efficiency of data storage is on the bottom rung of things i care about here. most important is to free up some space, any space. even the inefficient way should yield about 8k, which is far more than i need.
thanks for the help, though, i really do appreciate it!
priority #1. i'm a beginner here, this is my first real project. i'm not interested in making the best thing ever, just a fun game that people can enjoy in a style that i like.Having the engine in the fixed bank makes the programming easier,
thanks for the help, though, i really do appreciate it!
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Progress Thread - Project Blue
It's a good thing we can throw lots of inexpensive ROM on the problem post compo.
- toggle switch
- Posts: 139
- Joined: Fri Sep 30, 2016 8:57 pm
Re: Progress Thread - Project Blue
do you just keep the 'fixed' bank in it's own file, and the use .incbin to include it in each separate bank, or what?
Re: Progress Thread - Project Blue
With regard to detailed working steps for causing ca65 and ld65 to emit the code repeated in all banks, I cannot give an authoritative answer for several hours because I am not at a PC on which my NESdev toolchain (Git, Make, cc65, Python, GIMP, Windows or Wine, and FCEUX debugger) is installed. In the meantime, I'll answer the following question:toggle switch wrote:do you just keep the 'fixed' bank in it's own file, and the use .incbin to include it in each separate bank, or what?
I'll sketch an untested trampoline for BNROM here for the purpose of estimating how many cycles it would use. Some changes are needed for GTROM because PRG bank is in the same port as CHR and nametable switching, but they should be minimal.FrankenGraphics wrote:Would trampolines like that be a significant increase in cycles?
Code: Select all
; These parts go in all banks, using a method to be determined
; once I am at a PC on which my NESdev toolchain is installed
; A ROM location reflecting which bank is currently switched in
current_bank: .byte I
.proc nmi_handler_prolog
pha
txa
pha
tya
pha
; Save previous bank
lda current_bank ; a location in ROM
pha
immediate_stmt:
lda #<.bank(nmi_handler_body)
sta immediate_stmt
jmp nmi_handler_body
.endproc
.proc nmi_handler_epilog
pla
tax
sta identity,x
pla
tay
pla
tax
pla
rti
.endproc
; This part goes in any bank
.segment "MUSICCODE"
.proc nmi_handler_body
; TODO: Push display list to OAM
; TODO: Push VRAM changes to CHR RAM and nametable
; TODO: Call music engine
; (Watch out for reentrancy when the NMI might interrupt
; changing music or playing a sound effect!)
jmp nmi_handler_epilog
.endproc
identity:
.byte 0, 1, 2, 3
The preceding doesn't include the logic to put the prolog and epilog in all banks. To make that, I would need to be at a PC on which my NESdev toolchain is installed.
It'd still need the trampolines in case graphics loading is interrupted, and it'd need the code for decompressing said graphics.FrankenGraphics wrote:A bank strictly for storing graphics to be loaded in bulk between levels wouldn't need it
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Progress Thread - Project Blue
i think there are three ways; somewhat combinable:do you just keep the 'fixed' bank in it's own file, and the use .incbin to include it in each separate bank, or what?
.segment [name] = anything between this directive and the next .segment can be used by the linker to paste the segment exactly where you want it in ROM. this should give you the most convenient control.
.inc or .include [filename] = includes a separate source file - you still need to define a .segment, though.
.incbin [filename] = includes a separate binary as raw data. This isn't preferable imo as you need to pre-assemble it. It's mainly for assets. also, .segment is still what gives you control over where it is placed in ROM.
Correct me if i'm wrong, but I don't think you can escape using .segment in any way without complicating stuff further.
Still, i've seen at least one instance where partytimehexcellent .incbinned the famitracker driver + music data at a specified ROM addr to be done with it. This was done to a simpler mapper using another assembler without a linker, though. with the cc65 suite (nesicide included), the linker is supposed to do ROM organization stuff for you - via the linker cfg.
Last edited by FrankenGraphics on Wed Feb 07, 2018 10:13 am, edited 1 time in total.
Re: Progress Thread - Project Blue
You could just code it as a 256K UNROM and then use membler's converter program to convert it into 512K GTROM.honestly, the efficiency of data storage is on the bottom rung of things i care about here
I put the code in a macro then do the fixed banks like this:toggle switch wrote:do you just keep the 'fixed' bank in it's own file, and the use .incbin to include it in each separate bank, or what?
Code: Select all
.macro fixed_impl
; fixed code goes here
.endmacro
.repeat 16, i
.segment .concat("FIXED", .string(i))
.byt i ; Store the current bank at $8000
.if i = 0
fixed_impl
.else
.scope .ident(.concat("dummy_scope", .string(i)))
fixed_impl
.endscope
.endif
.endrepeat
- toggle switch
- Posts: 139
- Joined: Fri Sep 30, 2016 8:57 pm
Re: Progress Thread - Project Blue
thanks. i think the macro method should work for me... wasn't aware i could have a macro containing procs and other macros.
Re: Progress Thread - Project Blue
It would be a waste to replicate the entire engine across multiple banks! What I normally do is put data and functions that use that specific type of data in their own banks. For example, all banks that contain level maps will include copies of the routines that prepare rows and columns of tiles for scrolling, and also the routines that test for collisions between objects and level geometry. Banks with compressed graphics will have a copy of the decompression routine, and so on. It's the same principle of having audio data in the same bank as the audio driver.
Switching banks in simple mappers like GTROM is pretty fast, so that shouldn't have a significant impact on your vblank bandwidth. I'm obsessed with making the most out of the vblank time myself, so I don't even push A, X and Y to the stack during an expected vblank, I only do it on unexpected vblanks (i.e. lag frames). This is controlled by a flag on bit 7 of some variable (e.g. "FrameRead") so that it can be checked with BIT without corrupting any registers. The main thread waits for vblank by waiting for this flag to be cleared, and the NMI handler will only bother saving A, X and Y when the flag is already clear when the NMI fires.
In the main thread:
In the NMI:
Something along these lines. Saves a few cycles if you're really really tight on vblank time.
As for simulating a fixed bank when using 32KB PRG-ROM switching, you can .include the "fixed bank", but you have to do something about the repeated labels. One thing you can do is include the file normally only once, and all other times surround it in a new scope:
It's similar to the macro approach, but with includes. If you need to put the bank number in each bank, you can just do .byte <.bank(*).
Switching banks in simple mappers like GTROM is pretty fast, so that shouldn't have a significant impact on your vblank bandwidth. I'm obsessed with making the most out of the vblank time myself, so I don't even push A, X and Y to the stack during an expected vblank, I only do it on unexpected vblanks (i.e. lag frames). This is controlled by a flag on bit 7 of some variable (e.g. "FrameRead") so that it can be checked with BIT without corrupting any registers. The main thread waits for vblank by waiting for this flag to be cleared, and the NMI handler will only bother saving A, X and Y when the flag is already clear when the NMI fires.
In the main thread:
Code: Select all
dec FrameReady ;$00 -> $ff
WaitForVblank:
bit FrameReady
bmi WaitForVblank
Code: Select all
HandleLagFrame:
pha
txa
pha
tya
pha
jmp DoCommonStuff
NMI:
bit FrameReady
bpl HandleLagFrame
;(update vram)
DoCommonStuff:
;(handle the scroll and other raster effects)
;(handle audio updates)
inc FrameReady
beq Return
dec FrameReady
pla
tay
pla
tax
pla
Return:
rti
As for simulating a fixed bank when using 32KB PRG-ROM switching, you can .include the "fixed bank", but you have to do something about the repeated labels. One thing you can do is include the file normally only once, and all other times surround it in a new scope:
Code: Select all
.segment "FIXED0"
.scope
.include "fixed.asm"
.endscope
.segment "FIXED1"
.scope
.include "fixed.asm"
.endscope
.segment "FIXED2"
.scope
.include "fixed.asm"
.endscope
;(...)
.segment "FIXED15"
.include "fixed.asm"
Re: Progress Thread - Project Blue
ld65 does not support pasting a segment to multiple places.