CA65/LD65: putting common code in several banks

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

User avatar
neilbaldwin
Posts: 481
Joined: Tue Apr 28, 2009 4:12 am
Contact:

CA65/LD65: putting common code in several banks

Post by neilbaldwin »

Hoping someone has a suggestion on how to do this elegantly as I can't fathom it out.

I want to split my editor over two 16k banks (MMC1) but there are some common routines and tables that need to be accessed whichever of the two PRG banks the editor is currently in.

My idea was to duplicate these common elements in both editor banks at the same address so you can just call them from anywhere in the editor. Problem is, the labels end up being duplicated too and you get errors of course.

However, I'm unsure about how to go about it. I was trying to mess around with .export and .import but I got myself in a lot of knots. I was hoping there'd be a simple way to do it in the memory configuration file for ld65.

Any advice would be very welcome. :)
mic_
Posts: 922
Joined: Thu Oct 05, 2006 6:29 am

Post by mic_ »

If the code is location-independent you could compile it separately and incbin the resulting binary at all the places where you need it.

Another solution would be to create macros of them (essentially making all local labels anonymous).
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Compile and link it separately, and write out a map file. In your main program, incbin the result in its own SEGMENT. Write a native program (in C, Perl, Python, etc.) to translate the map file into a file that the main program can include.
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

I just tried this and it works well, assuming I'm understanding the question. First, assemble each segment separately, so that only the exported symbols can clash. Then just include the duplicated code in each segment but don't export its names. No incbin or anything. Full source (uses 16-byte segments to make it easy to see in hex editor): ca65_segments.zip

I noticed Neil mentioned he wants the controller routines at the SAME addresses in each. To achieve this, include them at the beginning of the segments, so they always assemble the same in each.

Code: Select all

; controller.s
    read_joy:
        ...
        rts

; common.s
    .export table
    
    .segment "COMMON"
    table:
        ...

; seg1.s
    .export foo
    .import table
    
    .segment "SEG1"
        
        .include "controller.s"
        
    foo:
        ...
        jsr read_joy
        ...
        lda table,x
        ...
        rts

; seg2.s
    .export bar
    .import table
    
    .segment "SEG2"
        
        .include "controller.s"
        
    bar:
        ...
        jsr read_joy
        ...
        lda table,x
        ...
        rts

; build
ca65 common.s
ca65 seg1.s
ca65 seg2.s
ld65 common.o seg1.o seg2.o
User avatar
neilbaldwin
Posts: 481
Joined: Tue Apr 28, 2009 4:12 am
Contact:

Post by neilbaldwin »

Thanks for the help and ideas.

With blargg's example I managed to get the code building but have ran into another problem. All of my ZP/RAM vars are defined in one .h file and so I now need to .export everything so the other 'modules' can use the vars.

The only option seems to be to .export every variable and then .import the definitions (or use the auto-import switch for ld65?) but as I've got about 600 variables defined, if there's a less labour-intestive way to avoid having to change the whole .h to to export everything, I'd really appreciate some info.

Or could I turn the .h file into a 'library'?

:)
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Hello neilbaldwin,

I also use ca65 / ld65 for my nes game development. While I don't have the same routine in multiple ROM banks at the same address, I do have fixed locations for all of my ZP (and non ZP) global variables. (ps- I'm using a MMC1 based design).

I don't know if the source to my project would help you or not, but you (or anyone else) are free to look at it. Of particular interest for this thread would be "src/kernel/globals.s" and "src/include/globals.inc".

https://svn.ecoligames.com/svn/nes-game/trunk
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

neilbaldwin wrote:All of my ZP/RAM vars are defined in one .h file and so I now need to .export everything so the other 'modules' can use the vars.
The typical pattern in C is to "define" the variables (reserve space for them) in a single .c file and "extern declare" them in a .h file that .c files include:

Code: Select all

/* stuff.h */
extern unsigned short foo;
extern unsigned char bar[128];

/* stuff.c */
#include "stuff.h"
unsigned short foo;
unsigned char bar[128];
This works in ca65 too, as the cc65 system has pretty much the same linkage model as C, apart from the difference between 1-byte and 2-byte pointers:

Code: Select all

; stuff.h
.globalzp foo
.global bar

; stuff.s
.include "stuff.h"
.segment "ZEROPAGE"
foo: .res 2
.segment "BSS"
bar: .res 128
The only option seems to be to .export every variable and then .import the definitions (or use the auto-import switch for ld65?) but as I've got about 600 variables defined, if there's a less labour-intestive way to avoid having to change the whole .h to to export everything
If you know sed, you can write a one-liner to extract a list of variables and prepend .global or .globalzp as needed. If you don't, you can do the same thing using the regular expression support built into Perl or Python.
User avatar
neilbaldwin
Posts: 481
Joined: Tue Apr 28, 2009 4:12 am
Contact:

Post by neilbaldwin »

Groan....

So, I've type ".export" at the front of all my variables and now I'm using the corresponding ".import" in another file to access them but I seem to be getting a lot of "range errors" e.g

cpx #keyBufferEnd-keyBufferStart

where keyBufferStart and keyBufferEnd are addresses in main RAM.

I'm guessing the resulting arithmetic operation is returning a number that exceeds 8bits but how do I know and more importantly, how do I force it to be correct?

Pllllllease don't tell me I've wasted about 3 hours today trying to make this work :S
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

neilbaldwin wrote:Groan....

Pllllllease don't tell me I've wasted about 3 hours today trying to make this work :S
No matter what solution you choose, you have not wasted 3 hours. At best you've used 3 hours to solve your problem. At worse you've used three hours to learn something :)
User avatar
Memblers
Site Admin
Posts: 3901
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Post by Memblers »

Maybe you could try cpx #<(keyBufferEnd-keyBufferStart) to force the lower 8 bits. What a pain, though.

edit - I think I remember doing something like this by putting the duplicated data inside a .scope, but with another label before it (outside the scope) to reference the thing.

But I've done all sorts of weird things with LD65 to get around this sort of stuff, inserting the duplicate data is easy once everything is just anonymous binary data. I think this can be done in the linker, you can make a segment and just output it to the file multiple times. Segment sizes have to be adjusted manually, though.

I didn't know that for a while with LD65 (I guess because 32kB was usually plenty), that having everything output to the same file will simply build up the entire file in the order you link it.
User avatar
neilbaldwin
Posts: 481
Joined: Tue Apr 28, 2009 4:12 am
Contact:

Post by neilbaldwin »

Memblers wrote:Maybe you could try cpx #<(keyBufferEnd-keyBufferStart) to force the lower 8 bits. What a pain, though.

edit - I think I remember doing something like this by putting the duplicated data inside a .scope, but with another label before it (outside the scope) to reference the thing.

But I've done all sorts of weird things with LD65 to get around this sort of stuff, inserting the duplicate data is easy once everything is just anonymous binary data. I think this can be done in the linker, you can make a segment and just output it to the file multiple times. Segment sizes have to be adjusted manually, though.

I didn't know that for a while with LD65 (I guess because 32kB was usually plenty), that having everything output to the same file will simply build up the entire file in the order you link it.
Yeah, I fixed that cpx just like that. Horrible though.

I managed to get the ROM to build but now it doesn't run (black screen).

Something is wrong somewhere but jesus, it could be anything at this stage :(
ReaperSMS
Posts: 174
Joined: Sun Sep 19, 2004 11:07 pm

Post by ReaperSMS »

I usually just used .global and .globalzp, rather than import/export.
User avatar
Banshaku
Posts: 2404
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

Like mentioned above, every new trial will bring some experience in the end so I wouldn't see it as a waste of time. If that was the case, I would be crying a lot over all the re-factoring I did recently ;)

But right now, the problem is maybe you changed too much thing without knowing the impact of that change. In assembler, this can sometime be lethal (I experienced it many time ;;^_^). My suggestion, although abstract, would be that you first roll back to a previous version that work and do the operation on a few variables that you know would have a visual impact right away if they're not working properly. During that phase, don't focus on the code sharing but on the variable sharing only, which seem to be the one causing issue. Once you figure out the cause, you can reproduce the same thing with another group of variable. You should do it gradually since maybe you did some miss (typo or something) and now you cannot figure out where it is. Save every interim revision until the end, just in case something goes wrong during the process.

I had a few issue like that when converting the FT driver. I was doing it in one shot and couldn't figure out where did I miss. Doing it gradually should help.

It may not be the answer that you're looking for but maybe the process could help in the end figuring out what went wrong.
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Here's a doc and example code I came up with that cleanly solves what I understand to be the problem: ca65_duped_bank_data.zip
User avatar
neilbaldwin
Posts: 481
Joined: Tue Apr 28, 2009 4:12 am
Contact:

Post by neilbaldwin »

It's always something stupid isn't it....

In all the code reorganising I'd put the audio engine in the wrong bank so the CPU was executing garbage instead of initialising the APU :)

Well at least it seems to work now, even though my code now looks like a china shop in the wake of a visit by Mr Bull.

Oh and blargg's last post is a really nice elegant solution so after all my .export/.import typing, I'm probably going to scrap it and do it his way.
Post Reply