It is currently Thu Aug 16, 2018 3:13 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 67 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next
Author Message
PostPosted: Wed Feb 07, 2018 8:33 am 
Offline

Joined: Fri Sep 30, 2016 8:57 pm
Posts: 82
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.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 8:37 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20408
Location: NE Indiana, USA (NTSC)
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.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 8:41 am 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 332
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:
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;
}



Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 8:53 am 
Online
Formerly WheelInventor

Joined: Thu Apr 14, 2016 2:55 am
Posts: 1701
Location: Gothenburg, Sweden
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.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:20 am 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 332
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.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:32 am 
Online
Formerly WheelInventor

Joined: Thu Apr 14, 2016 2:55 am
Posts: 1701
Location: Gothenburg, Sweden
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?

Quote:
Each fixed byte takes up 16 bytes in the ROM, which is a lot!

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.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:47 am 
Offline

Joined: Fri Sep 30, 2016 8:57 pm
Posts: 82
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.

Quote:
Having the engine in the fixed bank makes the programming easier,


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.

thanks for the help, though, i really do appreciate it!


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:51 am 
Online
Formerly WheelInventor

Joined: Thu Apr 14, 2016 2:55 am
Posts: 1701
Location: Gothenburg, Sweden
It's a good thing we can throw lots of inexpensive ROM on the problem post compo.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:54 am 
Offline

Joined: Fri Sep 30, 2016 8:57 pm
Posts: 82
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?


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 9:58 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20408
Location: NE Indiana, USA (NTSC)
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?

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:

FrankenGraphics wrote:
Would trampolines like that be a significant increase in cycles?

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.

Code:
; 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


An NMI handler that does significant work will already be saving and restoring AXY. The parts of this not in a "typical" NMI handler are saving the old bank, switching to the new bank, and the JMP in and out of the body, and restoring the old bank, which total about 30 cycles.

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.

FrankenGraphics wrote:
A bank strictly for storing graphics to be loaded in bulk between levels wouldn't need it

It'd still need the trampolines in case graphics loading is interrupted, and it'd need the code for decompressing said graphics.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 10:08 am 
Online
Formerly WheelInventor

Joined: Thu Apr 14, 2016 2:55 am
Posts: 1701
Location: Gothenburg, Sweden
Quote:
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 think there are three ways; somewhat combinable:

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

_________________
http://www.frankengraphics.com - personal NES blog


Last edited by FrankenGraphics on Wed Feb 07, 2018 10:13 am, edited 1 time in total.

Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 10:11 am 
Offline
User avatar

Joined: Thu Mar 31, 2016 11:15 am
Posts: 332
Quote:
honestly, the efficiency of data storage is on the bottom rung of things i care about here

You could just code it as a 256K UNROM and then use membler's converter program to convert it into 512K GTROM.

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 put the code in a macro then do the fixed banks like this:

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


I've had problems when I tried the include/incbin approach.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 10:13 am 
Offline

Joined: Fri Sep 30, 2016 8:57 pm
Posts: 82
thanks. i think the macro method should work for me... wasn't aware i could have a macro containing procs and other macros.


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 12:04 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10713
Location: Rio de Janeiro - Brazil
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:
Code:
  dec FrameReady ;$00 -> $ff
WaitForVblank:
  bit FrameReady
  bmi WaitForVblank


In the NMI:
Code:
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

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:

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

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(*).


Top
 Profile  
 
PostPosted: Wed Feb 07, 2018 12:17 pm 
Offline

Joined: Tue Oct 06, 2015 10:16 am
Posts: 778
ld65 does not support pasting a segment to multiple places.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 67 posts ]  Go to page Previous  1, 2, 3, 4, 5  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


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:  
cron
Powered by phpBB® Forum Software © phpBB Group