Over the years, I've developed some techniques to make my ASM6 code more organized, but as my projects grow bigger, it becomes harder to keep track of everything, and some design decisions require me to propagate changes across several files, in addition to some other minor annoyances. So now I'm again wondering if I could improve my workflow if I switched to CA65. Looking at it again, it doesn't look as complex as I used to think it was, and some of the features I've been reading about sound pretty useful to me. However, there are some things I'm used to doing in ASM6 that I absolutely can't live without, and I can't find equivalent examples for CA65. So I decided to ask here, and list some of the things I need, to see if anyone can tell me how I can do them in CA65.

1- Permanent variables vs. temporary variables:

Some parts of my program are active the whole time, such as the video, audio and input systems, so their variables don't share RAM with anything else, but other parts, such as cutscenes and gameplay, are temporary, so the RAM that comes after the permanent stuff is shared between the parts that don't run concurrently. In ASM6 I have a "memory counter" symbol for each RAM page that's updated for the permanent stuff, but not for the temporary stuff. How would I do this is CA65?

2- Fake fixed bank of flexible size:

Working with a mapper that switches 32KB PRG-ROM pages, I need a block of code/data to be present in the end of every bank. In ASM6, I mark the start and the end of such block with labels, and the difference between these labels gives me the size of the block, which I can subtract from $10000 to .org the block to the end of each bank. What would be the CA65 equivalent to this?

3- Repeated labels:

There's code and data that must be included multiple times (in different banks), but labels can't be redefined, so instead of using labels I save the current PC in a symbol, which can be redefined. What's the correct way to approach this in CA65?

4- What's the deal with .import and .export?

This one has nothing to with ASM6, it's just that I still can't figure out what exactly a "module" is in CA65, and when .import and .export are needed. Can anyone sum it up for me?

This is all I can think of right now, and if I can get around these issues I might be able to convert my code to CA65. I probably forgot something, so I may need to ask more questions later. I don't plan on wasting much time on this though, so if it ends up being too complicated I might not go through with it. We'll see. Thanks in advance for helping me out.

1. Local labels (any label starting with @) are only accessible between non-local labels (any label starting with a letter). See: ca65 labels and constants.

2. Anonymous labels with relative branching. Using : as a label you can branch forward to the next label with bcc :+ or backward to the previous label with bcc :-, or you can also branch two or three labels away with something like bcc :+++.

3. You can use the .scope directive to place a block of code inside an enclosing scope. See ca65 scope. If you want to access variables from another scope, you can use C++ style :: to address them.

ca65 separates assembling and linking. If your entire program is one big assembly program, you don't need import or export, you assemble into a single "object", then link that object into a ROM.

If you break your program into separate files, and assemble each of them separately, you end up with one object per thing you assembled, and then you use the linker to combine them all together into a ROM. The import and export directives are how objects communicate with each other; when being assembled they know nothing of what is going on in one another.

Being able to do things in separate assemblies might actually remove your problems with scope in question 3, by the way, since anything that is not exported will not conflict with similarly named things in other files.

Some parts of my program are active the whole time, such as the video, audio and input systems, so their variables don't share RAM with anything else, but other parts, such as cutscenes and gameplay, are temporary, so the RAM that comes after the permanent stuff is shared between the parts that don't run concurrently. In ASM6 I have a "memory counter" symbol for each RAM page that's updated for the permanent stuff, but not for the temporary stuff. How would I do this is CA65?

In the linker script, you'd make three BSS (uninitialized RAM) memory areas, each with a start address and size, and a segment for each. Segments in the same memory area must not overlap, but segments in different areas may.

One segment BSS is for permanent things.

One segment GAMEBSS is for gameplay.

One segment CUTBSS is for cutscenes.

The gameplay and cutscene RAM segments would be allowed to overlap in memory. Then to allocate into one of these segments, you use the .segment command before a segment.

Working with a mapper that switches 32KB PRG-ROM pages, I need a block of code/data to be present in the end of every bank. In ASM6, I mark the start and the end of such block with labels, and the difference between these labels gives me the size of the block, which I can subtract from $10000 to .org the block to the end of each bank. What would be the CA65 equivalent to this?

In the linker script, you make a MEMORY area for each bank, a SEGMENT for the majority of the bank, and SEGMENT for the copy of the last kilobyte in each bank. For example, in a 4 Mbit oversize BNROM, ROM would comprise 16 MEMORY areas and 32 SEGMENT declarations.

In the definition of the repeated last kilobyte, you'd fill the unused portion using a .res command, where .res number_of_bytes fills that many bytes. You calculate the number of bytes by taking the desired address (such as $3FA bytes after the start of the last kilobyte) and subtracting the current address.

There's code and data that must be included multiple times (in different banks), but labels can't be redefined, so instead of using labels I save the current PC in a symbol, which can be redefined. What's the correct way to approach this in CA65?

In the .s file that produces the repeated last kilobyte, you make a .macro that spits out one copy of the last kilobyte and call it once for each of the 16 last-kilobyte segments. Hide all copies but one in a .scope that hides its labels from the rest of the program so that you don't get duplicate symbol errors.

Quote:

4- What's the deal with .import and .export?

This one has nothing to with ASM6, it's just that I still can't figure out what exactly a "module" is in CA65, and when .import and .export are needed. Can anyone sum it up for me?

Instead of each .s file including other .s files with .include, each .s file is assembled separately to an object file containing relocatable code. This consists of machine code followed by instructions to resolve symbols that are defined in other object files. Finally, the locations are fixed up afterward by a linker that combines the object files and resolves their relocations.

The .import keyword declares that a symbol is defined not in this file but instead in another object file.The .export keyword declares that a symbol shall be made available for other object files to use.The .global keyword acts like .export if the symbol is defined in this file and .import otherwise.

Now, I'm sorry, but you might want to reword the title, because it's pretty confusing, maybe change it to "How do I do things in CA65 that I do in ASM6?".

Edit: I was writing this, and I then had to go do something, and when I had came back, a ton of people had already posted probably better explanations than I have...

tokumaru wrote:

1- Permanent variables vs. temporary variables:

I guess this is how you'd do temporary:

Whatever: .res (however many bytes this is)

and this is permanent:

Whatever = (wherever you want it to go).

The temporary ones will simply go wherever it fits in ram, so if I had Whatever1 and Whatever2 with Whatever2 being after Whatever1 in the list, Whatever2 would start at the value of whatever size Whatever1 is.

I can't really help you on this one, because I don't think I've dealt with anything like this before on the SNES.

tokumaru wrote:

3- Repeated labels:

I honestly don't have a clue what you mean. (sorry for not being all that helpful...)

tokumaru wrote:

4- What's the deal with .import and .export?

It's just for if you want to import and export variables between files that get assembled (otherwise, the assembler will tell you that it is undefined and will refuse to assemble everything). Here's an example of what I mean:

The other thing I didn't mention above, because I was just starting to explain linking, but I like to link each 32k bank separately, and just concatenate them into your ROM when finished.

1. Avoids most of the need for things like including the same file multiple times and using scopes, etc.2. Separates all the generated debugging info, which I like to parse into FCEUX debugging files.3. Assemble fixed/shared code only once, using the same generated object in the link step for each bank. (Dunno if this is really an advantage, but I like the idea that the code was only assembled once even though it's used in multiple banks.)4. You can use the same linker config file for all the banks. This means it can be a simpler config, instead of trying to specify e.g. 8 parallel versions of the same thing within one config file, you just reuse it 8 times for 8 link steps. (For example, if the "temporary" RAM suggestion from before is also tied to your bank structure, you could do it with just 1 MEMORY/SEGMENT instead of 1 for each page.)

I see... But it seems you have to break it all into blocks of known sizes beforehand, instead of having the temporary parts move around automatically as the fixed parts grow/shrink from me adding/removing variables, like I do with my current approach of using symbols to mark the end of the fixed parts?

Quote:

FIXED: load = PRG, type = ro, start = $FF90;

Again, you have to manually adjust the start depending on how much fixed stuff you have? There's no way of saying "align this arbitrarily sized block to the end of the bank"? Maybe it's possible in a hackish way, but that's actually one of the things I'm trying to avoid.

Quote:

1. Local labels (any label starting with @) are only accessible between non-local labels (any label starting with a letter). See: ca65 labels and constants.

2. Anonymous labels with relative branching. Using : as a label you can branch forward to the next label with bcc :+ or backward to the previous label with bcc :-, or you can also branch two or three labels away with something like bcc :+++.

3. You can use the .scope directive to place a block of code inside an enclosing scope. See ca65 scope. If you want to access variables from another scope, you can use C++ style :: to address them.

But what if the label I need to repeat is the entry point of a function in the fake fixed bank (i.e. it has to be repeated in all banks but the label has to be global so it can be called from anywhere)?

Quote:

ca65 separates assembling and linking. If your entire program is one big assembly program, you don't need import or export, you assemble into a single "object", then link that object into a ROM.

Ah, I see.

Quote:

Being able to do things in separate assemblies might actually remove your problems with scope in question 3, by the way, since anything that is not exported will not conflict with similarly named things in other files.

Hum... In this case I would assemble each bank separately and export all the function and data labels I need to reference from outside of that bank? That could mean a LOT of exports for things such as animation frames and the like...

tepples wrote:

In the definition of the repeated last kilobyte, you'd fill the unused portion using a .res command, where .res number_of_bytes fills that many bytes.

OK. Ideally, I wouldn't have unused portions and the repeated part would grow automatically, but from the answers so far it seems that to take advantage of using segments I have to switch to a "blockier" way of organizing my code.

Quote:

In the .s file that produces the repeated last kilobyte, you make a .macro that spits out one copy of the last kilobyte and call it once for each of the 16 last-kilobyte segments. Hide all copies but one in a .scope that hides its labels from the rest of the program so that you don't get duplicate symbol errors.

Good idea.

Quote:

The .import keyword declares that a symbol is defined not in this file but instead in another object file.The .export keyword declares that a symbol shall be made available for other object files to use.The .global keyword acts like .export if the symbol is defined in this file and .import otherwise.

OK. Would it be acceptable for me to use hundreds of .export statements in a bank that contains patterns for hundreds of animation frames so I can create pointers to that data in the main engine in order to access it during vblank? Or is there a better approach for situations like this?

Espozo wrote:

Now, I'm sorry, but you might want to reword the title

Yeah, right after I posted I noticed it wasn't a question.

Espozo wrote:

Whatever = (wherever you want it to go).

I'd rather use NESASM than have to do this! That's the exact kind of thing I want to avoid... I want my code to be flexible, so I don't have to edit several lines of code every time I resize a variable or rename something.

rainwarrior wrote:

For example, if the "temporary" RAM suggestion from before is also tied to your bank structure, you could do it with just 1 MEMORY/SEGMENT instead of 1 for each page.

No, these are completely unrelated, but this is still a good suggestion.

OK. Would it be acceptable for me to use hundreds of .export statements in a bank that contains patterns for hundreds of animation frames so I can create pointers to that data in the main engine in order to access it during vblank? Or is there a better approach for situations like this?

Personally I tend to avoid .import/.export and use mostly .global. I do this in C style by placing the stuff I want to be "public" in a header with .global:

Code:

; foo.inc: The interface to module "foo"; (you usually want to have an include guard here as well).global something.global bar

Code:

; foo.s: The actual implementation of "foo".include "foo.inc"; The include pulls in the .global(s), which then turn the symbol definitions below into .exportssomething = 123.proc bar lda #123 rts.endproc

Code:

; main.s: Somebody using the "foo" module.include "foo.inc"; Since we're just referencing "bar" (not defining it), the .global turns this into an .importjsr bar

I think you should be fine with 100+ .exports, but sometimes you can simply include the data in as you would in any other assembler. It's a judgement call.

tokumaru wrote:

Quote:

FIXED: load = PRG, type = ro, start = $FF90;

Again, you have to manually adjust the start depending on how much fixed stuff you have? There's no way of saying "align this arbitrarily sized block to the end of the bank"? Maybe it's possible in a hackish way, but that's actually one of the things I'm trying to avoid.

AFAIK there's no built-in support for this. It might be possible to .export the block size from code, and then import it in the linker configuration, but I'm not sure if the linker requires "start" to be a constant (technically it shouldn't, but it may).

I believe I tried to do something similar with DMC samples, but I don't think I ever figured out a good solution.

I see... But it seems you have to break it all into blocks of known sizes beforehand, instead of having the temporary parts move around automatically as the fixed parts grow/shrink from me adding/removing variables, like I do with my current approach of using symbols to mark the end of the fixed parts?

You don't have to. These are additional constraints you're dropping on me now after the fact. There's a lot of different ways to do it.

tokumaru wrote:

Quote:

FIXED: load = PRG, type = ro, start = $FF90;

Again, you have to manually adjust the start depending on how much fixed stuff you have? There's no way of saying "align this arbitrarily sized block to the end of the bank"? Maybe it's possible in a hackish way, but that's actually one of the things I'm trying to avoid.

It was a solution I thought was perfectly fine, as my fixed bank code almost never changes, and of course, if I ever added too much to it, I'd get a linker error preventing me from compiling a bad ROM. If I wanted, I could also produce an error if there was any space left (.error directive allows you to make custom errors); I could even have the error tell me exactly what number to change it to take up the empty space.

I don't think there's an "align to end" feature, but there might be a way to import the segment size for use in linking somehow. I don't have a ready made solution for you that does exactly that, would take some digging to discover a good method, maybe. I don't know off the top of my head, sorry. Why exactly do you need "align to end"?

How do you accomplish "align to end" in ASM6, by the way?

tokumaru wrote:

But what if the label I need to repeat is the entry point of a function in the fake fixed bank (i.e. it has to be repeated in all banks but the label has to be global so it can be called from anywhere)?

I think I already said this, but you can use :: to specify the scope of a label from another scope. Take a look at the ca65 scope document I linked.

If you're talking about one scope per bank, then the labels for stuff in the fixed area are going to be the same in each bank; I don't think you'd need or want anything global for that?

tokumaru wrote:

Hum... In this case I would assemble each bank separately and export all the function and data labels I need to reference from outside of that bank? That could mean a LOT of exports for things such as animation frames and the like...

For things like common enumerations, I put them in a "header" file that I .include in several assemblies that need it. It's usually logical to group stuff that's related into a single assembly if you can, so that you don't have to have a lot of import/exports. It's the same as in C, what do you extern from the header, and what do you leave as static to the translation unit? (Also, your header can just use "global" instead of import or export, and you can use the same header for both assemblies.)

If you don't like having to export or import things at all, you can just go back to putting everything in one big assembly too. That still works fine.

tokumaru wrote:

Would it be acceptable for me to use hundreds of .export statements in a bank that contains patterns for hundreds of animation frames so I can create pointers to that data in the main engine in order to access it during vblank? Or is there a better approach for situations like this?

I'm not entirely certain what situation you are describing. Does your main engine really need to know about hundreds of different pointers to animation data? That seems to imply that every animation has unique code in the main engine that refers to its specific data. If that's the case, why not just include that data in the same assembly as that code?

The primary use of import and export is for function labels. You can use it for data labels too, if you need to, but I usually find that data is consumed in one place and doesn't need to be available globally? You can also use import and export for constants and enumeration values, if you want to, but I would likely just create a common header file for these, like I would normally do in C.

I've looked at it a bit, and I can't find a good way to do an "align to end" feature with the existing ca65/ld65 tools. I think it would really have to be a new feature added to the linker.

The start address of a segment has to be a constant before linking begins, so you can't use the segment's size (generated by the linker) to adjust its start.

I also thought about trying to do it by just padding it up against the end of the available space, but similarly, while you can import linker-generated constants (like segment size) and use them in assembly code (to be filled in during the link stage), you can't use link-time symbols with features like .res or .repeat that could be used to generate padding. They have to be known at assembly time.

I'm still very curious how the equivalent feature looks in ASM6, though.

Still, even without this feature, I think your desire to right-align the segment is really just an optimization, right? I can only think of three times you'd want to adjust the start value:1. The size you allocated was too small, and you need to increase it. (Linker will give an overflow error.)2. The size of the rest of the data in the bank was too large, and you need to optimize. (Again, an overflow error.)3. You want to group the unused space for aesthetic value.

Also, I'm not sure that I made this clear, but you don't have to specify the "start" attribute for all segments, this was specifically for something you want at a fixed location (like your fixed code, or vectors). If you omit "start", they automatically adjust their size and position to fit into the lowest space available, in the order they're listed. I'm not sure if you were complaining that you thought every segment would have to specify a start address manually; most of the time they don't.

I guess that CA65 is flexible enough that you guys that are used to it can come up with creative ways to do things, but to me this model is still a bit alien. I don't know how much you can bend the rules.

Quote:

It was a solution I thought was perfectly fine, as my fixed bank code almost never changes, and of course, if I ever added too much to it, I'd get a linker error preventing me from compiling a bad ROM. If I wanted, I could also produce an error if there was any space left (.error directive allows you to make custom errors); I could even have the error tell me exactly what number to change it to take up the empty space.

Yes, it works... I can certainly adjust the size near the end of development to create a perfect fit, but this goes against the fluidity I'm going for. My main goal is to prevent changes from creating the need to change something else, whenever possible.

Quote:

I don't think there's an "align to end" feature, but there might be a way to import the segment size for use in linking somehow. I don't have a ready made solution for you that does exactly that, would take some digging to discover a good method, maybe. I don't know off the top of my head, sorry.

If you do ever find out, please do share.

Quote:

Why exactly do you need "align to end"?

It's not that I absolutely need it, I just want things to be fluid from the very start, and I can just add code and data to the correct places without having to manually tell the assembler how big these places are. This would make it easier to reuse a template for different projects, for example.

Quote:

How do you accomplish "align to end" in ASM6, by the way?

Code:

.org $10000 - (BlockEnd - BlockStart)

BlockStart:

;STUFF

BlockEnd:

I guess it works because even though the assembler doesn't know the final values of BlockStart and Blockend, it can still calculate the difference because it knows the size of the code between them.

Quote:

I think I already said this, but you can use :: to specify the scope of a label from another scope.

So I would scope all instances of the repeated code and name only one of them, and I'd use that to access the labels inside?

[/quote]If you're talking about one scope per bank[/quote]One scope per bank was a bad idea I guess, considering that I need to have parts of the same sub-system (video, audio, etc.) scattered across different banks, and it would be silly to break up the sub-systems like that.

Quote:

If you don't like having to export or import things at all, you can just go back to putting everything in one big assembly too. That still works fine.

I guess I'd be more comfortable with that, and making use of scoping to deal with the repeated code.

Quote:

I'm not entirely certain what situation you are describing. Does your main engine really need to know about hundreds of different pointers to animation data? That seems to imply that every animation has unique code in the main engine that refers to its specific data. If that's the case, why not just include that data in the same assembly as that code?

Sorry, I guess I didn't explain it well. Say that in banks that contain CHR data I use labels to identify the tiles that belong to each animation frame. Then, when the game engine is processing animation, it will need access to these labels to set up the pattern transfers that will copy the tiles to VRAM. Now that I realized that one assembly per bank would be a bad idea in my case, I guess this won't be a problem, since I can indeed have everything related to the animations in the same assembly.

Quote:

The primary use of import and export is for function labels. You can use it for data labels too, if you need to, but I usually find that data is consumed in one place and doesn't need to be available globally? You can also use import and export for constants and enumeration values, if you want to, but I would likely just create a common header file for these, like I would normally do in C.

That makes sense. I guess I just have to get more used to this model of programming.

This would generate padding from the current point up to BlockStart. In this case you would have BlockStart in the same segment as the one you're padding, though. If you went that route, I'd highly recommend and assert or other error generator to make sure your vectors end up aligned, e.g.:

I'm used to the idea of just using segments to do my alignment work, rather than trying to write my own assembly code to produce padding, but this kind of thing should work okay. You can still get all the other features/power of ca65 even if you're not taking much advantage of the linker.

Last edited by rainwarrior on Sun Nov 01, 2015 11:45 pm, edited 1 time in total.

I've looked at it a bit, and I can't find a good way to do an "align to end" feature with the existing ca65/ld65 tools.

Thanks for looking into it.

Quote:

3. You want to group the unused space for aesthetic value.

That's pretty much it, and not having to go back and change numbers in a file that was supposed to be final just because I edited a tiny bit of code in another file.

Quote:

I'm not sure if you were complaining that you thought every segment would have to specify a start address manually; most of the time they don't.

My complaint was having to specify the start of the "fixed bank", but mainly the temporary variables. I'm really short on RAM, and fitting all the features I need in 2KB of RAM required a bit of Tetris work. Because of this I have things like 95% of a memory page occupied by object slots, which are permanent, and a dozen bytes in the end which can be used for whatever purpose the different game modes need... say, checkpoint data (player's X, Y, etc.) for resuming gameplay, for example. If I have to make that very specific separation in the config file, I'm making the contents of that file dependent on my variable declarations, which doesn't feel right. In ASM6 I do this:

Maybe I can do something similar in a hackish way, like defining temporary variables as offsets and and adding them to the memory counters, or something like that. Or create an overlapping segment for temporaries and fill it backwards using the same .res trick that puts the "fixed bank" in the end of the bank (or pad it until the last variable of the permanent segment).

rainwarrior wrote:

Oh, well you can do the equivalent in ca65:

Cool.

Quote:

In this case you would have BlockStart in the same segment as the one you're padding, though.

And I would have to make sure that was the very last thing in the segment, right? I guess I could use each bank's segment, instead of creating segments for the fixed bank. It's certainly less robust, but as long as I'm careful it should be OK.

Quote:

Just to double check on your padding, you might also use asserts to verify all the fixed banks are the same

OK.

Last edited by tokumaru on Sun Nov 01, 2015 11:41 pm, edited 1 time in total.

Also, to follow up what I meant about not having to have fixed sizes/positions for your temporary RAM pages, here's another way to do it:

Have one MEMORY/SEGMENT for your shared stuff, and then one each for the temporary pages, and just allocate space on the bottom of each temporary page to match the space taken up by the shared stuff. Basically you end up with several overlapping segments that all address the same space, but with some checks here and there you can make them all align the same way.

Have one MEMORY/SEGMENT for your shared stuff, and then one each for the temporary pages, and just allocate space on the bottom of each temporary page to match the space taken up by the shared stuff.

Hah, this is exactly the idea I had and just edited my post to include it (although I might have not worded it very well)! Good to know that I'm starting to think the right way to work with CA65.

Thanks for all the tips, rainwarrior and tepples. I guess I have enough to attempt porting my code. I'm pretty sure I'm going to have some problems with scoping, so I might show up with a few more questions later. I think the scope problems will be easier to solve, though.

And I would have to make sure that was the very last thing in the segment, right? I guess I could use each bank's segment, instead of creating segments for the fixed bank. It's certainly less robust, but as long as I'm careful it should be OK.

Actually, if you put the padding and fixed code in one segment by itself, all you have to do is put that segment last in your SEGMENTS list in the config file. (Each SEGMENT line is packed into its corresponding MEMORY region in the same order they're listed.)

The actual order of segments in your assembly files doesn't matter, though the individual code that fills up each segment will appear in the order it's written of course, like any other assembler. (When linking multiple objects that are using the same segments, the order of files will maker a difference here.)

Who is online

Users browsing this forum: Majestic-12 [Bot] and 7 guests

You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forumYou cannot post attachments in this forum