This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

How do I do in CA65 things I do in ASM6?

How do I do in CA65 things I do in ASM6?
by on (#158249)
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.
Re: How to do in CA65 things I do in ASM6
by on (#158250)
tokumaru wrote:
1- Permanent variables vs. temporary variables:

If they all need to be in the same link, perhaps have overlapping MEMORY regions for the temporary area:
Code:
MEMORY areas in linker config:

BSS_PAGED_0:      start = $200,  size = $100,  type = rw, file = "";
BSS_PAGED_1:      start = $200,  size = $100,  type = rw, file = "";
BSS_PAGED_2:      start = $200,  size = $100,  type = rw, file = "";

SEGMENTs in linker config:

RAM_PAGED_0:      load = BSS_PAGED_0,    type = bss;
RAM_PAGED_1:      load = BSS_PAGED_1,    type = bss;
RAM_PAGED_2:      load = BSS_PAGED_2,    type = bss;

in assembly code:

.segment "RAM_PAGED_0"
temporary_variable_byte: .res 1
temporary_variable_array: .res 64


tokumaru wrote:
2- Fake fixed bank of flexible size:

Very simple:
Code:
linker config file, have a line like this in SEGMENTS:

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

assembly code:

.segment "FIXED"
; put your code here


tokumaru wrote:
3- Repeated labels:


There's a few ways.

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.

4. The .proc directive is a combination label + scope, intended for self contained procedures.

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

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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158251)
tokumaru wrote:
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?

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.
Code:
.segment "CUTBSS"
dialogue_ptr: .res 2
dialogue_countdown: .res 1
anim_keyframe_ptr: .res 2

.segment "GAMEBSS"
actor_xlo: .res NUM_ACTORS
actor_xhi: .res NUM_ACTORS


Quote:
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?

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.
Code:
start_of_fixed_bank:
  ; bunch of stuff

.res start_of_fixed_bank+$3FA - *
.addr nmi_handler, reset_stub, irq_handler


Quote:
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?

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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158252)
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... :lol:

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.

Like this:

(offset 0) Whatever1: .res 2
(offset 2) Whatever2: .res 4
(offset 6) Whatever3: .res 1
(offset 7) Whatever4: .res 2

tokumaru wrote:
2- Fake fixed bank of flexible size:

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:

Code:
File 1:

Whatever1:  .res 2

Whatever2 = 4

.export Whatever1
.export Whatever2

====================

File 2:

.import Whatever1
.import Whatever2
Re: How do I do in CA65 things I do in ASM6?
by on (#158253)
tepples wrote:
Code:
.res start_of_fixed_bank+$3FA - *
.addr nmi_handler, reset_stub, irq_handler


Instead of that .res line with some calculation in it, I just use the same tool I used to align the fixed code, i.e. segments.
Code:
SEGMENTS in linker config:

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

assembly:

.segment "FIXED"
; fixed code goes here

.segment "VECTORS"
.word nmi_handler, reset_stub, irq_handler



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.)
Re: How to do in CA65 things I do in ASM6
by on (#158256)
Thanks for the answers so far, guys.

rainwarrior wrote:
perhaps have overlapping MEMORY regions for the temporary area

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.

4. The .proc directive is a combination label + scope, intended for self contained procedures.

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.
Re: How to do in CA65 things I do in ASM6
by on (#158260)
tokumaru wrote:
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 .exports
something = 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 .import
jsr 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.
Re: How to do in CA65 things I do in ASM6
by on (#158261)
tokumaru wrote:
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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158270)
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.
Re: How to do in CA65 things I do in ASM6
by on (#158272)
rainwarrior wrote:
You don't have to.

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.
Re: How to do in CA65 things I do in ASM6
by on (#158274)
tokumaru wrote:
Quote:
How do you accomplish "align to end" in ASM6, by the way?

Code:
   .org $10000 - (BlockEnd - BlockStart)
BlockStart:
   ;STUFF
BlockEnd:


Oh, well you can do the equivalent in ca65:
Code:
.res ($10000 - (BlockEnd - BlockStart)) - *

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.:
Code:
.assert * = $FFFA, error, "Vector table unaligned!"
.word vector0, vector1, vector2

Just to double check on your padding, you might also use asserts to verify all the fixed banks are the same, e.g.:
Code:
.assert Bank0::BlockStart = Bank1::BlockStart, error, "Bank 1 fixed block misaligned"
.assert Bank0::BlockStart = Bank2::BlockStart, error, "Bank 2 fixed block misaligned"
etc...


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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158276)
rainwarrior wrote:
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:

Code:
   MemoryCounterPage3 = $300

   ;permanent
   .enum MemoryCounterPage3
   ScrollX .dsb 2
   ScrollY .dsb 2
   Score .dsb 4
   MemoryCounterPage3 = $
   .ende

   ;temporary
   .enum MemoryCounterPage3
   CameraX .dsb 2
   CameraY .dsb 2
   CheckpointX .dsb 2
   CheckpointY .dsb 2
   .ende

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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158277)
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.
Code:
MEMORY

BSS:              start = $200, size = $600,  type = rw, file = "";
BSS_PAGED_0:      start = $200, size = $600,  type = rw, file = "";
BSS_PAGED_1:      start = $200, size = $600,  type = rw, file = "";

SEGMENTS

RAM:              load = BSS,            type = bss, define = yes;
RAM_PAGED_0:      load = BSS_PAGED_0,    type = bss;
RAM_PAGED_1:      load = BSS_PAGED_1,    type = bss;


Code:
.segment "RAM"
shared_ram_start:

shared_a: .res 1
shared_b: .res 3
etc...

shared_ram_end:
SHARED_RAM_SIZE = shared_ram_end - shared_ram_start

.segment "RAM_PAGE_0"
.res SHARED_RAM_SIZE ; overlaps with shared RAM allocations
.assert (__RAM_SIZE__ + __RAM_START__) = *, error, "Ram page 0 misaligned" ; double check at link time (makes sure you didn't accidentally put extra stuff in RAM segment)
temp_a: .res 1 ; reused by other RAM pages
temp_b: .res 25
etc.

.segment "RAM_PAGE_1"
.res SHARED_RAM_SIZE
.assert (__RAM_SIZE__ + __RAM_START__) = *, error, "Ram page 1 misaligned"
temp_c: .res 1 ; reuses same space as temp_a above
temp_d: .res 1
etc.

You could put scopes around your temporary page allocations if you wanted, or needed to use variables with the same name between them, etc.
Re: How do I do in CA65 things I do in ASM6?
by on (#158280)
rainwarrior wrote:
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.
Re: How do I do in CA65 things I do in ASM6?
by on (#158281)
tokumaru wrote:
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.)
Re: How do I do in CA65 things I do in ASM6?
by on (#158282)
Oh, you're right! This is really starting to make sense to me! :D
Re: How to do in CA65 things I do in ASM6
by on (#158283)
Oops, did a little test, and I think I overstated the ability of .res, ha ha. Again, .res needs to be constant at assemble time, but it also needs to be constant in the first pass, it seems, which puts a few more requirements on us. The whole bank needs to be in one assembly unit, this way (not necessarily in the same file, but all files must be combined into one unit with .include). Everything for the ROM except the fixed code must go in one big segment. In this example, the segment is named "BANK".

Code:
.segment "BANK"
bank_start: ; this must be the first line in the BANK segment so that its total size can be calculated

; other code can go here

; the FIXED code can go anywhere, as long as appears before the padding at the end of BANK which appears below
; the critical thing is that FIXED_SIZE must be known before we get to the padding, so that we can calculate a size for padding
.segment "FIXED"
fixed_start:
; fixed code here
.assert * = $FFFA, error, "Vectors misaligned"
.word vec0, vec1, vec2
FIXED_SIZE = * - fixed_start

; finally the padding at the end of BANK segment
; this must appear after FIXED_SIZE is defined
.segment "BANK"
BANK_SIZE = * - bank_start
.res $10000 - (FIXED_SIZE + BANK_SIZE) ; generate padding


Trying to do the alignment via padding rather than the linker ends up requiring you to use a single SEGMENT and a single assembly for the whole bank, but maybe that's okay for you because that's how other assemblers do things anyway.
Re: How do I do in CA65 things I do in ASM6?
by on (#158287)
Two things to keep in mind about ca65:

- It doesn't know where the code is going to be placed (unless you explicitly state it with .org), but it needs to know the size of the data/code that it has generated. So stuff like .res $1234-* won't work in relocatable ("non-org") mode.
- It's a single-pass assembler, so if you try to reference labels that haven't been defined yet, it may or may not work, depending on the case:

Code:
BlockStart:
nop
nop
BlockEnd:
; Fine, the value is known at this point.
.res BlockEnd-BlockStart

Code:
; Error, need to know value (affects amount of generated data)
.res BlockEnd-BlockStart
BlockStart:
nop
nop
BlockEnd:

Code:
; Fine, value not known but doesn't affect amount of generated data, so
; actual calculation can be deferred.
.byte BlockEnd-BlockStart
BlockStart:
nop
nop
BlockEnd:

Sometimes it would be nice if it could resolve expressions like that (in cases that are resolvable, i.e. no recursive definitions and so on), but usually it's not a deal breaker.
Re: How to do in CA65 things I do in ASM6
by on (#158291)
rainwarrior wrote:
Again, .res needs to be constant at assemble time, but it also needs to be constant in the first pass, it seems, which puts a few more requirements on us.

Thanks for verifying this. Honestly though, this isn't such a big issue, so if the whole thing gets too awkward I might just manually define the size of the fixed block and adjust later, to make proper use of the assembler's capabilities. The main reason I decided to try CA65 is that I want to use segments so I can group relevant pieces of code regardless of where in the ROM they'll end up. In ASM6 I have to always mind the order in which code is written and/or files are included, so everything ends up where it should, and this is getting annoying to maintain because each sub-system has parts scattered everywhere.

thefox wrote:
It's a single-pass assembler

Will keep that in mind, thanks.

Anyway, here's another question: How can I keep track of the indices of the different program banks? For example, if I placed a subroutine in a segment called "BANK13", how can I know which bank to switch to in order to call that subroutine without having to manually type the number 13 to switch banks? The goal is to not have to track down references to that bank in case I decide to move the routine to a different bank later on. In ASM6, where I do everything linearly, I just have a symbol incremented every time a new bank starts, and I copy the value from that symbol to another symbol I can use for switching banks (e.g. AUDIO_BANK = CURRENT-BANK).
Re: How to do in CA65 things I do in ASM6
by on (#158294)
tokumaru wrote:
Anyway, here's another question: How can I keep track of the indices of the different program banks? For example, if I placed a subroutine in a segment called "BANK13", how can I know which bank to switch to in order to call that subroutine without having to manually type the number 13 to switch banks? The goal is to not have to track down references to that bank in case I decide to move the routine to a different bank later on. In ASM6, where I do everything linearly, I just have a symbol incremented every time a new bank starts, and I copy the value from that symbol to another symbol I can use for switching banks (e.g. AUDIO_BANK = CURRENT-BANK).

There's an attribute "bank" that can be specified for memory areas in the linker configuration. It's an arbitrary 32-bit number that can be retrieved from symbols with .bank in source code.

E.g. if you have a memory area "PRG13" that specifies "bank=13", and you have a segment "BANK13" that you place in "PRG13", then:
Code:
.segment "BANK13"
.proc foo
  rts
.endproc

; ...
; somewhere else:

lda #.lobyte( .bank( foo ) ) ; loads 13 (have to use .lobyte since it's a 32-bit number)
jsr mapBank
jsr foo

This works across file boundaries (the correct bank is substituted at link time).
Re: How to do in CA65 things I do in ASM6
by on (#158307)
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.

Correct. I've never seen a linker that allows "right justifying" code, or specifying the location based on the address of the end of a segment rather than based on the start of a segment. Do you want me to go request this feature in ld65 on the cc65 project's issue tracker on GitHub?

Quote:
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?

Yes. I did something like this for level data in a recent project.

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

Then I guess I've elicited another possible ld65 feature request from you: "union segments". Consider RAM segments A and D that are usable at all times and RAM segments B and C are not usable simultaneously. You want to lay out BSS segments B and C at the end of BSS segment A, wherever that might end up, and BSS segment D that starts at the maximum of the end of segments B and C, wherever that might end up.
Code:
,---------------.
|               |
|       A       |
|               |
+-------+-------+
|       |       |
|   B   |       |
|       |   C   |
`-------+       |
        |       |
,-------+-------+
|               |
|       D       |
|               |
`---------------'


rainwarrior wrote:
These are additional constraints you're dropping on me now after the fact.

tokumaDRW?
Re: How to do in CA65 things I do in ASM6
by on (#158325)
thefox wrote:
There's an attribute "bank" that can be specified for memory areas in the linker configuration.

Ah, that's perfect! That's the kind of thing I'm looking for, built-in functionalities to do things that required convoluted hacks in other assemblers.

tepples wrote:
Do you want me to go request this feature in ld65 on the cc65 project's issue tracker on GitHub?

If you feel like this might be useful to a reasonable number of users, then by all means, go ahead. I personally wouldn't feel comfortable making feature requests at this point, since I'm not really acquainted with the software yet.

Quote:
Quote:
Would it be acceptable for me to use hundreds of .export statements

Yes. I did something like this for level data in a recent project.

Good to know that this isn't such an alien thing to do, but I'd still try to avoid it, if possible.

Quote:
"union segments"

Sounds interesting.

Quote:
tokumaDRW?

Oh man, I'll try harder to not come off like that. I definitely didn't make up things after the fact though, since all of this stuff is already implemented and working in ASM6. I might have failed to provide all the details though, so sorry about that! :wink:
Re: How to do in CA65 things I do in ASM6
by on (#158335)
tokumaru wrote:
Would it be acceptable for me to use hundreds of .export statements

There's really no problem with using hundreds of import/export/global directives, if you want to. It's not like they're "expensive", it's just another symbol in a table somewhere. The linker just looks 'em up and sticks the data where it needs to go.

From a code design standpoint, there's probably something to be said for interfaces that are well contained, so that everything that belongs together is in the same translation unit, and your cross-unit communication (import/export) is minimal. However, you've got a real program to write, and if lots of linked symbols seems like it would help, there's no pressing reason to avoid it.

tepples wrote:
Do you want me to go request this feature in ld65 on the cc65 project's issue tracker on GitHub?

The strongest use case I can think of for "align to end" is DPCM samples. It's hard to let them resize to fit your data automatically, but also make room for stuff pushing up from below. As an alternative to "align to end", a "start at or above" feature would be just as good to meet its requirements, I think.

tepples wrote:
"union segments"

I can't really think of a compelling reason a feature like this would be needed. Yes, it would apply to tokumaru's specific request, well, but that needs that request represents can be satisfied in other ways, so I can't think of a more practical reason to want this feature. Actually, even "align to end" would be a good solution in this case, i.e. if you align all your temporary pages to end, you no longer need everything in the same translation unit for that ".res" to represent the shared space. You could just protect the bottom edge of your temporary space with a link-time .assert, and both regions could just grow toward the middle safely.

tokumaru wrote:
tepples wrote:
tokumaDRW?

Oh man, I'll try harder to not come off like that.

I certainly wasn't trying to make that comparison. I was merely explaining that if you had given me the additional constraints up front, I could have easily given you a solution that met them.
Re: How do I do in CA65 things I do in ASM6?
by on (#158344)
As long as we're talking about new features and what the linker can't do currently, when I was toying around with FME-7 I noticed this one case that the linker can't really handle: Let's say you have an 8 KB bank, and you want to map it at two different addresses in the memory space at (possibly) different times. So, for example, you have some code that should go at $8000, and another unrelated piece of code (or DMC sample) that should go at $C200, and you want to place those in the same bank. Since you can specify only one base address for the memory area, this can't be done.

Not sure what kind of a new feature would be the best way to model that type of a thing. Possibly another set of definitions for banks within the output file, and multiple memory areas could output data into the same bank (and the actual base address of a memory area would depend on how much stuff is in the bank by the time the memory area gets added there).

Probably not a big deal in practice, unless you're really short of space, but still kind of annoying that it's not at all possible, AFAIK (without .org).
Re: How do I do in CA65 things I do in ASM6?
by on (#158350)
thefox wrote:
Since you can specify only one base address for the memory area, this can't be done.

Segments have a "load" attribute (where it goes in MEMORY block) and "run" attribute (where it is expected to be run from, i.e. controls PC). By default "run" inherits "load", but otherwise you can use this to run a segment in a different MEMORY block than it gets stored in.

The only limitation I can think of is that you'd really need to start the segment at a specific address, so that you can line it up on both sides. Normally this feature is intended for run-from-RAM code, so the intention is that the linker will give you some defined symbols telling you where to place the code in RAM. With ROM, obviously that's not flexible anymore, so you'd have to specify it in the segment itself, I think.
Re: How do I do in CA65 things I do in ASM6?
by on (#158354)
rainwarrior wrote:
thefox wrote:
Since you can specify only one base address for the memory area, this can't be done.

Segments have a "load" attribute (where it goes in MEMORY block) and "run" attribute (where it is expected to be run from, i.e. controls PC). By default "run" inherits "load", but otherwise you can use this to run a segment in a different MEMORY block than it gets stored in.

Thanks for the comment. I actually think it helped me figure out a good solution for this:

Code:
# linker.cfg
MEMORY {
    # The actual bank that goes to the file.
    PRG0:
        start=$0000, # Address doesn't matter since it won't be used.
        size=$2000,
        fill=yes,
        file=%O;

    # A dummy memory area for data going to $8000, not written to file.
    PRG0_8000:
        start=$8000,
        size=$2000,
        define=yes,
        file="";

    # Another dummy area for data going to $C000, not written to file.
    # Start address and size depends on how much data got placed in PRG0_8000.
    PRG0_C000:
        start=$C000+(__PRG0_8000_LAST__-__PRG0_8000_START__),
        size=$2000-(__PRG0_8000_LAST__-__PRG0_8000_START__),
        file="";
}

SEGMENTS {
    CODE0_8000: load=PRG0, run=PRG0_8000, type=ro;
    CODE0_C000: load=PRG0, run=PRG0_C000, type=ro;
}


Code:
.segment "CODE0_8000"
.proc at8000
    jmp *
.endproc

.segment "CODE0_C000"
.proc atC000
    jmp *
.endproc


Result:
Code:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  4C 00 80 4C 03 C0 00 00 00 00 00 00 00 00 00 00  L.€L.À..........
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...
Re: How do I do in CA65 things I do in ASM6?
by on (#158355)
Interesting, that looks very useful!

I did not know about the LAST symbol, or that it could be considered a constant you could use for subsequent MEMORY blocks. I'd always run into issues trying to use generated values like that, but I'd always tried to do it through SEGMENTS; I guess the linker goes through the MEMORY blocks one at a time, in order, with each block's symbols generated before trying to resolve the next one?
Re: How do I do in CA65 things I do in ASM6?
by on (#158356)
rainwarrior wrote:
Interesting, that looks very useful!

I did not know about the LAST symbol, or that it could be considered a constant you could use for subsequent MEMORY blocks. I'd always run into issues trying to use generated values like that, but I'd always tried to do it through SEGMENTS; I guess the linker goes through the MEMORY blocks one at a time, in order, with each block's symbols generated before trying to resolve the next one?

Yeah I was not sure at first whether I would be able to use it there either. I'm glad it works. I'm not sure if there's an actual good reason why it doesn't work in segment definitions. There's a chance it's a bug.
Re: How do I do in CA65 things I do in ASM6?
by on (#158366)
How about scratchpad variables? In ASM6, I reserve 32 bytes in ZP that subroutines can use however they want. Then, for each subroutine, I use an .enum to define variables in that area (to keep in the spirit of not typing addresses manually, which is error-prone and annoying to maintain):

Code:
   .enum Scratchpad
   RowPointer .dsb 2
   BlockCount .dsb 1
   .ende

Any way to achieve similar behavior in ca65, without having to create hundreds of overlapping segments, one for each subroutine?
Re: How do I do in CA65 things I do in ASM6?
by on (#158367)
thefox wrote:
I noticed this one case that the linker can't really handle: Let's say you have an 8 KB bank, and you want to map it at two different addresses in the memory space at (possibly) different times. So, for example, you have some code that should go at $8000, and another unrelated piece of code (or DMC sample) that should go at $C200, and you want to place those in the same bank. Since you can specify only one base address for the memory area, this can't be done.

If you don't want to use multiple MEMORY areas or run addresses for segments in a bank, possibly because they're [tokumaru]not automatic[/tokumaru], I've thought of a third way. You can leave it at $8000, and then in your sample address table entry macro, map anything in $8000-$BFFF to $C000-$DFFF.

The reuse of the __*_LAST__ symbol looks interesting as well. If it works, it'd resolve one of the feature requests above. Is there a counterpart to GREATEST() or MAX() function in ld65 config language?
  • Union segments part I (multiple overlapping MEMORY areas starting after a specified previous MEMORY area, for BSS specific to one of several mutually exclusive parts of a program): I'll need to make a demo of resolving this with __*_LAST__
  • Union segments part II (MEMORY area starting after the longest of multiple overlapping MEMORY areas): Not resolved yet
  • SEGMENT aligned to end within its MEMORY area: Not resolved yet

I wonder whether scratchpad variables in $0000-$001F could be defined with a .struct.
Re: How do I do in CA65 things I do in ASM6?
by on (#158370)
tokumaru wrote:
Any way to achieve similar behavior in ca65, without having to create hundreds of overlapping segments, one for each subroutine?

In ca65 .enum has a different meaning, but .struct should be more or less equivalent. I would shy away from any solution that requires the linker configuration to be modified whenever a subroutine is added or removed.

tepples wrote:
Is there a counterpart to GREATEST() or MAX() function in ld65 config language?

The expressions allowed in the linker scripts seem to be more limited than in the assembler. Apparently only addition, subtraction, multiplication and division (and parentheses) are allowed (I didn't check the source, though).
Re: How do I do in CA65 things I do in ASM6?
by on (#158372)
tepples wrote:
I wonder whether scratchpad variables in $0000-$001F could be defined with a .struct.

Yeah, I was thinking of something along those lines.

Quote:
[tokumaru]not automatic[/tokumaru]

I don't know if this is a criticism (don't worry, I won't be offended if it is), but I fail to see how wanting things to be automatic could be a bad thing. This is not about laziness (even though saving time is always a good thing!), but about making things more flexible and error-free. Flexibility means you can more easily reuse code across different projects without worrying about adjusting lots of tiny little details in your templates. At the same time, not having to type redundant pieces of information, that can be deduced from things you actually do have to type, completely eliminates the chances of you screwing up and creating inconsistencies. Sure, it may be more complicated to setup at first, but in the long run you have less things to worry about.

thefox wrote:
I would shy away from any solution that requires the linker configuration to be modified whenever a subroutine is added or removed.

Precisely! I will look .struct up.
Re: How do I do in CA65 things I do in ASM6?
by on (#158373)
From my experience making two full size games with CA65, I find the most that I had to fiddle with linker configuration and rom layout came pretty early in the Nomolos project when I had little feeling for relative sizes of different types of code and data. So I basically lined everything up as I added them to the project. This resulted in having to juggle my rom layout fairly frequently, editing many files for .segment "ROMXX" and constants/luts for specifying which bank various objects reside in. After that first project I developed better habits for organizing data, dedicating certain roms exclusively to bg graphics, spr graphics, animations, music, engine extensions, and lookup tables. In short, once you get used to it I don't think that you'll feel it gets in your way. I can't say for sure how it would really differ from ASM6 for a large project, having never created a large project with ASM6. One thing that's nice about CA65 is it is pretty easy to stuff things in a few different banks all in one file just with the .segment directive. The linker config basically abstracts away all the .org, .pad that you would normally have to do in an ASM6 program. My feeling is it must be a big time saver.
Re: How do I do in CA65 things I do in ASM6?
by on (#158380)
I don't think I'd bother trying to make the linker sort out unions on such a small scale. You can always just create alias labels. Maybe it's not as convenient as .res but for individual functions you probably don't have a lot of allocations to make. Doing it manually shouldn't be too onerous:
Code:
; alllocate scratchpad
.segment "ZP": zeropage
scratchpad: .res 32

...

; alias new allocations from scratchpad for each function as needed

.proc myfunc
param_a = scratchpad + 0
param_b = scratchpad + 1
temp_c  = scartchpad + 2
    ; code goes here
    rts
.endproc

; bonus: using .proc puts these aliases in their own scope, so they don't pollute the global space, and you can also access them from outside the function:

    stx myfunc::param_a
    sty myfunc::param_b
    jsr myfunc


Edit: ZP segment must be identified as zeropage.
Re: How do I do in CA65 things I do in ASM6?
by on (#158382)
Yeah, that's the first solution that came to mind, and I do consider using it if I can't find a better one. Most subroutines use so few variables that this shouldn't be particularly annoying to manage.
Re: How do I do in CA65 things I do in ASM6?
by on (#158383)
I think the idea of the whole "union" thing is that the entire game needs certain variables, while other variables apply only to certain game phases, such as title screen, cut scenes, level intro and outro screens, and in-game. In RHDE, I had to use a ton of alias labels for memory maps that apply only to one of help screens, furnish phase, battle phase, and build phase.
Re: How do I do in CA65 things I do in ASM6?
by on (#158387)
Another question: what are the rules for naming the MEMORY and SEGMENT entries in the config file? All examples I could find don't repeat names in those two sections, not even for the header, which is just 1 simple entry in each section. I don't want to use abbreviations (e.g. HDR) or make arbitrary modifications to the names before understanding what the best practices are.
Re: How do I do in CA65 things I do in ASM6?
by on (#158388)
Ah, that's a good question actually. My above example has an error in it. If you have a segment called "ZEROPAGE" it will be automatically assumed by the assembler to be a zeropage segment.

Otherwise you have to manually do it:
Code:
.segment "ZP": zeropage


If you forget, you get absolute addressing for everything. (Hopefully you'd catch this when getting an assembly error when trying to use a pointer, etc.)

Other than that, there's no special names for segments or memory blocks unless you want to use cc65 (it expects a bunch of specifically named segments by default, but they can all be changed). MEMORY regions can share the same names as SEGMENTS (i.e. they're different namespaces), but I always keep them unique, myself, for no particular reason.

See: ca65 docs: .segment

If you use .segment once with zeropage, it should generate an error if you ever forget to use zeropage anywhere else (this is checked globally by the linker), so there is a good amount of safety checks against doing it wrong. As long as you remember at least once, you're safe.

I tend to put all my allocations together in one file, so I forget about these rules; I don't have to think about them often. :P Usually I just name the zeropage segment "ZEROPAGE" so I don't have to remember this rule.
Re: How do I do in CA65 things I do in ASM6?
by on (#158398)
rainwarrior wrote:
Otherwise you have to manually do it:
Code:
.segment "ZP": zeropage

Wait, what's the point of having type = zp in the segment definitions then?

Quote:
Usually I just name the zeropage segment "ZEROPAGE" so I don't have to remember this rule.

I can't do that because ZP, like the rest of RAM, is divided into permanent and temporary sections, so I'll have several references to it. Do I need write : zeropage every time I switch to one of these segments? This isn't so bad, I just want to be sure it's necessary.
Re: How do I do in CA65 things I do in ASM6?
by on (#158399)
tokumaru wrote:
Wait, what's the point of having type = zp in the segment definitions then?

Not really certain, but the types are ro/rw/bss/zp. Both bss and zp seem to have the same function (i.e. they enforce no initialized data in the segment). I wish it also made the linker enforce the missing "zeropage" on .segment but it does not appear to. Actually, I think I'll suggest this to the list...

tokumaru wrote:
Do I need write : zeropage every time I switch to one of these segments? This isn't so bad, I just want to be sure it's necessary.

Yes, but as long as you remember to do it once, assembler/linker errors will enforce that you do it everywhere else that's needed.
Re: How do I do in CA65 things I do in ASM6?
by on (#158400)
That's not how I understand the docs. You only have to declare a segment as : zeropage the first time. The docs give this example:
Code:
.segment "ROM2"                 ; Switch to ROM2 segment
.segment "ZP2": zeropage        ; New direct segment
.segment "ZP2"                  ; Ok, will use last attribute
.segment "ZP2": absolute        ; zp2.s(4): Error: Segment attribute mismatch

So you might define each zeropage segment by switching to it once in a global file that all your .s files .include:
Code:
.pushseg
.segment "ZP1": zeropage
.segment "ZP2": zeropage
.segment "ZP3": zeropage
.popseg
Re: How do I do in CA65 things I do in ASM6?
by on (#158401)
Ah, I didn't realize subsequent uses of .segment with no qualifier will assume the previous state. I was just going by some quick tests I threw together, i.e.
Code:
; this creates an error:

.segment "ZP" ; no specification: assumes absolute
.segment "ZP": zeropage ; error

; VS this apparently does not:

.segment "ZP": zeropage
.segment "ZP" ; OK: assumes previous usage
Re: How do I do in CA65 things I do in ASM6?
by on (#158420)
thefox wrote:
tepples wrote:
Is there a counterpart to GREATEST() or MAX() function in ld65 config language?

The expressions allowed in the linker scripts seem to be more limited than in the assembler. Apparently only addition, subtraction, multiplication and division (and parentheses) are allowed (I didn't check the source, though).

Possible solution for this one (this is getting a little bit hacky) is to export a symbol containing the necessary information from assembly code, and then import that in the linker config:
Code:
.import __FOO_LAST__
; bogus expression, but full expression evaluation capabilities of the assembler available
; (can be used to implement stuff like MIN/MAX/...)
.export whatever = (__FOO_LAST__ < $8100)*$1234

Code:
# linker.cfg
SYMBOLS { whatever: type=import; }

I tested this, and it seemed to work as expected, i.e. if the imported symbol was used before the "FOO" memory area had appeared in the config, it caused an error. And if it was used after (when the value of __FOO_LAST__ is known), it worked.
Re: How do I do in CA65 things I do in ASM6?
by on (#158459)
I have one more question: How do I go about making sure that timing-sensitive code doesn't cross page boundaries? So far I've used a macro to detect page crossing and generate an error in case it does happen, and then I'd manually shuffle subroutines around to prevent the situation. What is the recommended approach in ca65?
Re: How do I do in CA65 things I do in ASM6?
by on (#158460)
tokumaru wrote:
How do I go about making sure that timing-sensitive code doesn't cross page boundaries?

.align 256 is one option. Another is
Code:
.assert >function_end = >function, error, "I wanted Animal Crossing, not Page Crossing"
Re: How do I do in CA65 things I do in ASM6?
by on (#158476)
tepples wrote:
.align 256

Does that just pad the ROM until the next 256-byte boundary is reached or does the program try to be smart and fill the space with things from the same segment that might fit?
Re: How do I do in CA65 things I do in ASM6?
by on (#158477)
Also, the idea of using overlapping segments for the temporary RAM is getting a little out of hand. The RAM is already broken in 8 pages, so if I end up needing 8 non-permanent modules in my program that would mean 64 entries in each of the MEMORY and SEGMENT sections of the config file.

Another possibility that comes to mind is creating only one set of MEMORY/SEGMENT definitions for all of the temporary modules, and use .org statements (e.g. .org PERMANENT_RAM_PAGE_3_SIZE) to have the temporary variables start after the permanent ones. One problem is that I'd have to make these memory sections large enough to accommodate all variables from all temporary modules, instead of using their actual sizes. This sounds pretty kludgy to me, but it might just work and significantly de-clutter some files.
Re: How do I do in CA65 things I do in ASM6?
by on (#158479)
Align will pad up to the alignment boundary. The linker is NOT allowed to re-order your code to fit; each segment is filled up in the order that material is given to it (i.e. in the order .o files are specified to the linker, then within each .o file in the order the code appeared in the file).

It also requires you to put "align" in the segment definition too, I think? (see docs)

tokumaru wrote:
Also, the idea of using overlapping segments for the temporary RAM is getting a little out of hand. The RAM is already broken in 8 pages, so if I end up needing 8 non-permanent modules in my program that would mean 64 entries in each of the MEMORY and SEGMENT sections of the config file.

I don't understand, why would they multiply?

Each segment can be used in as many modules as you like. They're not per-assembly, they're per-link.

tokumaru wrote:
use .org statements

There is .org in ca65, but it has some things to be aware of. (see docs) By default it acts globally, meaning that switching to another .segment doesn't reset it (unless you enable per-segment org as a special feature). When you're done with .org located code, use .reloc to return to the normal way of laying down code.

In general, using .org makes it really hard for the linker to do its job without putting in a lot of padding. See my earlier statement about how it is not allowed to re-order stuff. If you do use it, try to keep it confined to its own segments, and don't mix org-located code with relocatable code in the same segment.
Re: How do I do in CA65 things I do in ASM6?
by on (#158481)
tokumaru wrote:
tepples wrote:
.align 256

Does that just pad the ROM until the next 256-byte boundary is reached or does the program try to be smart and fill the space with things from the same segment that might fit?

It's not smart at all. First of all it's applied at assembly time (the "align" specification in the linker config is used to do some sanity checks).

I have made some macros, like one to detect page-crossing branches, and another to align such that it will let me know how much space was wasted for padding. It would be nice, though, if ".align" acted more like a constraint ("this code must appear at an address multiple of 256") rather than an explicit instruction.

This is actually also one of those things that could use some improvement. Applying the align at link time would be preferable, I think.

rainwarrior wrote:
each segment is filled up in the order that material is given to it (i.e. in the order .o files are specified to the linker, ...

I'm not so sure it gives the guarantee about preserving the order of the object files. Then again I doubt it's specified anywhere, it's just not something I would rely on. (Obviously it has to preserve the ordering within the file.)
Re: How do I do in CA65 things I do in ASM6?
by on (#158502)
rainwarrior wrote:
Align will pad up to the alignment boundary. The linker is NOT allowed to re-order your code to fit;

I see... Well, you probably wouldn't want it to do that most of the time anyway.

Quote:
It also requires you to put "align" in the segment definition too, I think? (see docs)

Apparently.

Quote:
I don't understand, why would they multiply?

There are 8 pages of RAM ($000-$0ff, $100-$1ff, $200-$2ff, $300-$3ff, $400-$4ff, $500-$5ff, $600-$6ff and $700-$7ff), and I use separate segments for them because there are several times when I need to use particular pages for certain variables/arrays, mainly to avoid page crossing. I can't simply define a huge segment of RAM and fill it linearly. Because of that, for each temporary module of my program, I have to create 8 new segments. I'll probably stick to this model though... it may clutter the configuration steps, but the code itself is cleaner.

Quote:
When you're done with .org located code, use .reloc to return to the normal way of laying down code.

Yes, I did that and it worked fine.

Quote:
In general, using .org makes it really hard for the linker to do its job without putting in a lot of padding.

This isn't a problem in this case, since I'd be using it for RAM. I just have to make these segments long enough to accommodate all the padding and all the temporary variables.

thefox wrote:
It would be nice, though, if ".align" acted more like a constraint ("this code must appear at an address multiple of 256") rather than an explicit instruction.

Maybe they could include a new directive for that.

Quote:
I'm not so sure it gives the guarantee about preserving the order of the object files. Then again I doubt it's specified anywhere, it's just not something I would rely on.

I'll try to remember to not rely on that in case I use multiple object files.
Re: How do I do in CA65 things I do in ASM6?
by on (#158695)
So, with the help that was given to me in this thread I was able to implement most of what I had working previously in ASM6. I've hidden all the complicated stuff behind macros, so the actual source code looks cleaner. The macros became much shorter once I found out about .IDENT, and started generating label and symbol names dynamically. I do have one question regarding this, though: Is there a way I can "cache" the identifiers generated by .IDENT? Having to generate them all the time results in really long lines with a lot of redundancy... I considered using .DEFINE, but that doesn't really seem appropriate (also, the docs discourage you from using .DEFINE altogether).

Anyway, the only thing that I wasn't able to do yet is the "scratchpad union". That actually works nearly the same as the temporary variables, in that different subroutines use the same memory for different variables because they never run concurrently, which means that I could implement it by declaring a shitload of scratchpad areas in the config file and having each subroutine use its own segment. The problem is that you can only have 254 segments in a single object file, and considering how many segments I'm already using for other purposes, that limit sounds quite reachable.

If anyone has any ideas on how to make the declaration of scratchpad variables more dynamic, please share. I'm gonna try a couple things myself, but if nothing interesting comes up I might just end up manually adding offsets to the base address of the scratchpad area, something I really didn't want to do, but... oh well.
Re: How do I do in CA65 things I do in ASM6?
by on (#158703)
tokumaru wrote:
So, with the help that was given to me in this thread I was able to implement most of what I had working previously in ASM6. I've hidden all the complicated stuff behind macros, so the actual source code looks cleaner. The macros became much shorter once I found out about .IDENT, and started generating label and symbol names dynamically. I do have one question regarding this, though: Is there a way I can "cache" the identifiers generated by .IDENT? Having to generate them all the time results in really long lines with a lot of redundancy... I considered using .DEFINE, but that doesn't really seem appropriate (also, the docs discourage you from using .DEFINE altogether).

It's not possible to make "string constants", so really .define is the only reasonable choice here. Note that you can use .undefine if you need to avoid name clashes (e.g. if you do it in macros). Unfortunately this does mean that the expression will get re-evaluated again and again, but it's not a big problem. IMO the docs are overly anal about discouraging .define, because like #define in C, sometimes it's the best tool for the job.

Quote:
If anyone has any ideas on how to make the declaration of scratchpad variables more dynamic, please share. I'm gonna try a couple things myself, but if nothing interesting comes up I might just end up manually adding offsets to the base address of the scratchpad area, something I really didn't want to do, but... oh well.

You can do macro tricks. Without macros you can do something like this:
Code:
.segment "BSS"
scratch: .res 16

.segment "CODE"
.proc foo
  .struct ; anonymous struct, so symbols are placed in parent scope
    xyzzy .byte
    bar .word
  .endstruct

  lda scratch+xyzzy
  sta scratch+bar+1
  rts
.endproc


With macros you could get something like this:
Code:
.segment "BSS"
scratch: .res 16

.segment "CODE"
.proc foo
  start_locals ; this would be a macro that starts a struct, it could have a name like "locals"
    xyzzy .byte
    bar .word
  end_locals ; another macro for closing the struct

  ; "local" could be a .define macro defined as something like:
  ;   .define local(p) scratch+locals::p
  lda local xyzzy
  sta local bar+1
  rts
.endproc

This way is not really technically much different from adding to the base variable, but it does show intent better, IMO. You can also do some safety checks in "end_locals" (e.g. does the size of the struct fit within the scratch area).

EDIT: Split up long line.
Re: How do I do in CA65 things I do in ASM6?
by on (#158713)
thefox wrote:
.define is the only reasonable choice here.

OK.

Quote:
Note that you can use .undefine if you need to avoid name clashes (e.g. if you do it in macros).

I actually started doing that, but it looked worse than with all the redundant expressions, so I left it how it was. I'll try not to obsess over this, as long as it's working.

Quote:
With macros you could get something like this:

That's a great idea!

[tokumaDRW]There's just one thing I have to try, which is starting the labels from somewhere else than scratch. This will be necessary in functions that are called from other functions, when I need the inner function's locals to be after the outer function's.[/tokumaDRW]

anyway, thanks for the great idea of combining macros and .define. I'll see what I can do with that idea.
Re: How do I do in CA65 things I do in ASM6?
by on (#158714)
tokumaru wrote:
[tokumaDRW]There's just one thing I have to try, which is starting the labels from somewhere else than scratch. This will be necessary in functions that are called from other functions, when I need the inner function's locals to be after the outer function's.[/tokumaDRW]

Here's one option:
Code:
.struct foo
  padding .res 10 ; or whatever other size
  bar .byte
  ; ...
.endstruct

("start_locals" could also take the padding amount as a parameter and add it automatically.)
Re: How do I do in CA65 things I do in ASM6?
by on (#158718)
thefox wrote:
Here's one option:
Code:
.struct foo
  padding .res 10 ; or whatever other size
  bar .byte
  ; ...
.endstruct



Just to add, I'm pretty sure you can omit the name:

Code:
.struct foo
  .res 10 ; or whatever other size, or expression you want here (if it is constant at assemble time)
  bar .byte
  ; ...
.endstruct
Re: How do I do in CA65 things I do in ASM6?
by on (#158719)
Great idea!

thefox wrote:
("start_locals" could also take the padding amount as a parameter and add it automatically.)

That would be the way to go! The padding amount would actually be the size of the outer function's struct, which I can apparently get with .sizeof. I might have the start_locals macro receive a struct as an optional parameter, in which case I would pad as many bytes as the struct takes.

Movax12 wrote:
Just to add, I'm pretty sure you can omit the name

Makes sense, thanks.
Re: How do I do in CA65 things I do in ASM6?
by on (#158721)
FWIW ca65 does have a .union directive [ss]which seems to be mostly omitted from its documentation[/ss]. It works just like .struct except every line in a union starts at offset 0. The typical use of union appears to be to host overlapping structs.

I don't think it would really help, here, since struct is essentially just a collection of offset enumerations. I don't know if you'd get much value from sticking multiple structures into a union, but at least the concept is there in the language, somewhat.

Edit: The documentation I usually get to via google search is apparently just outdated. Union is documented here: http://cc65.github.io/doc/ca65.html#ss11.101
Re: How do I do in CA65 things I do in ASM6?
by on (#158747)
This single-pass thing is really annoying... It forces me to declare ZP variables before writing any code that accesses such variables, otherwise the assembler won't use ZP addressing. The program modules have to access each other's variables, so it's not like I can include the modules in a specific order to guarantee that ZP variables will only be used after they've been declared. Is there any way around this? I'm considering putting all functions inside macros, so I can write the code near the variable declarations, but delay the actual assembly of the code until absolutely all variables have been declared, but that sounds a little extreme...
Re: How do I do in CA65 things I do in ASM6?
by on (#158748)
If you .importzp a variable from another module, it'll be treated as zero page.

You can also switch to a segment, define static variables used by a subroutine, and switch back before the subroutine's body.
Code:
.segment "ZEROPAGE"
var1: .res 2
var2: .res 1
var3: .res 1

.segment "CODE"
.proc something
  lda (var1),y
  rts
.endproc

.segment "ZEROPAGE"
var4: .res 2
var5: .res 1
var6: .res 1

.segment "CODE"
.proc something_else
  lda (var4),y
  rts
.endproc
Re: How do I do in CA65 things I do in ASM6?
by on (#158753)
tepples wrote:
If you .importzp a variable from another module, it'll be treated as zero page.

I'm not sure I'm ready to assemble the modules separately, because there's a lot of communication between them. I'm sure I'm going to get lost in import/export hell if I try this.

Quote:
You can also switch to a segment, define static variables used by a subroutine, and switch back before the subroutine's body.

That's what I figured, but having the variable declarations away from the function's body is very undesirable. They wouldn't need to be apart if the variables were used exclusively in their respective function, but there are several cases where parameters are passed to (and returned by) the subroutines in the scratchpad area, so one function might need access to another function's variables, meaning I'd have to put ALL functions after ALL variables, making the code hard to read. The only solution I could come up with was to wrap all functions in macros (not only the ones that go in the "fixed bank" and need to be output multiple times), so that I can still type them near their respective variables, but without outputting anything at that time. Then, after all variables have been declared, a bunch of macro calls would put the routines wherever they need to be. Is that too much of a hack?

I feel like I'm having to jump through a lot of hoops to have everything automated the way I'm used to. I considered switching to ca65 because I thought I could make everything more versatile and dynamic, but while I can certainly see that the segment stuff, the macro system and the built-in functions are indeed very useful, I'm finding that the single pass aspect severely limits my options for arranging code, data and variables. I'm certain that most of my problems originate from me approaching this with the wrong mindset though, since I'm not used to object files, linking and that sort of thing.
Re: How do I do in CA65 things I do in ASM6?
by on (#158756)
I just put all my RAM allocations in a single file, and import everything from there. Everything includes this file. When I assemble the file, I define RAM_EXPORT which causes all the allocations and exports to be made, but elsewhere it is included and just creates the necessary imports.

Code:
.macro RESZP label, size
   .ifdef RAM_EXPORT
      label: .res size
      .exportzp label
   .else
      .importzp label
   .endif
.endmacro

.macro RES label, size
   .ifdef RAM_EXPORT
      label: .res size
      .export label
   .else
      .import label
   .endif
.endmacro

.segment "ZEROPAGE"

RESZP  i,                     1
RESZP  j,                     1
RESZP  k,                     1

.segment "OAM"
RES    oam,                   256

.segment "RAM"
RES    collision,             256
RES    dog_data0,             16
RES    dog_data1,             16
RES    dog_data2,             16


If I am using temporary variables in functions, and I wanted a "helpful" name for them, I'd just alias them there:

Code:
.proc my_func
param_a = i
param_b = j
param_c = k
   lda param_a
   eor param_b
   clc
   adc param_c
   rts
.endproc


Your problem seems to stem specifically from wanting to use aliased zeropage variables before the definition of the function. I think you I'd just do a "forward declaration" for that variable if it ever came up, but really it hasn't for me. (Forward declarations are a common necessity in C/C++, so I'm used to doing that already.) The stuff I tend to alias is always in BSS, and I'm used to using the common temporaries for ZP, they're all named like i, j, k, l, etc. and usually I document which ones a function uses or which ones it doesn't because there's a lot of overlap-- so I'd rather know a common name for them than hide it from myself with an alias.

The BSS stuff that I do alias is usually to do with different types of object, all of which use the same memory areas to store their state, but each of them has different data needs, so I alias the various bytes to document their usage. A struct might have done the job well, if I wasn't using striped arrays. At any rate, not really an issue because it's not on the zeropage, so it's not subject to the one-pass problem.
Re: How do I do in CA65 things I do in ASM6?
by on (#158768)
I also never had the problem because of the include/module architecture I've been using.

You can force zeropage addressing with .lobyte or "<", but if you were ever to move the variable outside zeropage, the hibyte would silently get truncated out. There's also a bug in the current version that gives a "suspicious address expression" warning when you use "z:<foo": https://github.com/cc65/cc65/issues/194

Actually, come to think of it, in this case you should be able to force zeropage addressing with "z:", and also get an error if it's not a zeropage address:
Code:
lda z:foo ; OK
foo = $55
lda z:bar ; error
bar = $123
Re: How do I do in CA65 things I do in ASM6?
by on (#158773)
Ah, good to know there's a way to force ZP addressing. Since I only need that for accessing a subroutine's local variables from outside that subroutine, which is a very particular case, I might just do that.

Anyway, I just thought of another way to implement overlapping variables. The idea is to use a huge dummy segment as a counter, defining dummy labels there so you can calculate the offset from the beginning and then add the offset to the base label in the actual segment that's used for memory. First, you need to reserve the bytes in the actual memory segment:

Code:
.segment "ZEROPAGE": zeropage
Scratchpad: .res 32 ;reserve 32 bytes for scratchpad variables

Then you need a couple of macros:

Code:
.macro StartScratchpadPage
   .segment "SCRATCHPAD": zeropage ;switch to the dummy segment, which is way bigger than 32 bytes
   .align $20 ;move on to the next "page"
   ;(could optionally add padding here so variables begin after another block of variables)
.endmacro

.macro DefineScratchpadVariable Name, Size
   .local Dummy
   .segment "SCRATCHPAD": zeropage
   Dummy: .res Size
   .ident(.string(Name)) = Scratchpad + (Dummy & $1f) ;ignore the higher bits of the offset when adding to the base address
.endmacro

Now you can start defining variables:

Code:
StartScratchpadPage
DefineScratchpadVariable foo, 2
DefineScratchpadVariable bar, 2

StartScratchpadPage
DefineScratchpadVariable baz, 2
DefineScratchpadVariable qux, 2

Now both foo and baz point to $00, while both bar and qux point to $02.

Again, this feels a little hacky to me, but if adding offsets manually is acceptable, I don't see why calculating the offsets automatically before adding wouldn't be OK.

The biggest advantage over using structs is that you don't need anything fancy to use these variables, they're just like regular labels, as far as the assembler is concerned.

I haven't worked out the details yet, but I'm pretty sure this solution can also be applied to temporary variables in general, meaning I could have as many temporary modules as necessary without having to create new segments for each of them. They'd all use the same 8 dummy segments to calculate the offsets, and add the offsets to labels in the actual memory pages.
Re: How do I do in CA65 things I do in ASM6?
by on (#158886)
OK, I believe these are the final macros for creating scratchpad variables (please don't mind my long macro names):

Code:
;Starts a new block of scratchpad variables.
.macro Assembler_StartScratchpadVariables Offset
   .pushseg ;save the current segment
   .segment "SCRATCHPAD_RAM_BLOCKS" ;switch to a large dummy segment
   .align $20 ;move on to the next block
   : ;mark the start with a label
   .ifnblank Offset
      .res Offset ;skip the requested amount of bytes
   .endif
.endmacro

When calling this macro you can provide an offset, which is the number of bytes to skip before starting the new variables. This offset must be a constant number, which can be written manually or automatically generated from a previous scratchpad declaration.

Code:
;Declares a scratchpad variable in the current block.
.macro Assembler_DeclareScratchpadVariable VariableName, VariableSize
   .ident(.string(VariableName)) = Assembler_Scratchpad + <(* - :-) ;create the address by adding the base address to the offset from the start of the block
   .res VariableSize ;advance the amount of bytes used by the variable
.endmacro

"Assembler_Scratchpad" is a label in regular zero page, where 32 bytes are reserved.

Code:
;Ends the current block of scratchpad variables.
.macro Assembler_EndScratchpadVariables Offset
   .ifnblank Offset
      .ident(.string(Offset)) = <(* - :-) ;save the current offset in the specified symbol
   .endif
   .popseg ;restore the previous segment
.endmacro

When ending the declaration you can optionally save the current offset in a symbol, which you can use in a later declaration to have that block placed after this one.

Here's how the macros are used:

Code:
Assembler_StartScratchpadVariables
Assembler_DeclareScratchpadVariable Scratch0, 2
Assembler_DeclareScratchpadVariable Scratch1, 2
Assembler_EndScratchpadVariables

Assembler_StartScratchpadVariables
Assembler_DeclareScratchpadVariable Scratch2, 3
Assembler_DeclareScratchpadVariable Scratch3, 1
Assembler_DeclareScratchpadVariable Scratch4, 2
Assembler_EndScratchpadVariables ScratchBlock0Size

Assembler_StartScratchpadVariables ScratchBlock0Size
Assembler_DeclareScratchpadVariable Scratch5, 2
Assembler_DeclareScratchpadVariable Scratch6, 1
Assembler_EndScratchpadVariables

The final addresses will be:

Scratch0: $00
Scratch1: $02
Scratch2: $00
Scratch3: $03
Scratch4: $04
Scratch5: $06
Scratch6: $08

You have to admit, this is a pretty good alternative to manually typing offsets, and you end up with the exact same kinds of labels, so you don't even need any special tricks for using these variables once they're declared.

I'm now working on a similar set of macros to deal with the reusable general RAM areas.

EDIT: Now that I think of it, the .align is probably unnecessary, since the offsets are calculated with subtractions. If that's the case, I might even be able to use the same dummy segment for scratchpad variables and for general RAM, since it's basically a space I can fill linearly.
Re: How do I do in CA65 things I do in ASM6?
by on (#158901)
Here's what I believe to be the final scratchpad RAM macros, in case anyone is interested:

Code:
;Starts a new block of scratchpad variables.
.macro Assembler_StartScratchpadVariables Offset
   .pushseg
   .segment "DUMMY"
   :
   .ifnblank Offset
      .res Offset
   .endif
.endmacro

Code:
;Declares a scratchpad variable in the current block.
.macro Assembler_DeclareScratchpadVariable VariableName, VariableSize
   .local Variable
   Variable: .res VariableSize
   .ident(.string(VariableName)) = Assembler_Scratchpad + (Variable - :-)
   .assert .ident(.string(VariableName)) + VariableSize <= 32, ldwarning, "Scratchpad RAM overflow."
.endmacro

Code:
;Ends the current block of scratchpad variables.
.macro Assembler_EndScratchpadVariables Offset
   .ifnblank Offset
      .ident(.string(Offset)) = <(* - :-)
   .endif
   .popseg
.endmacro
Re: How do I do in CA65 things I do in ASM6?
by on (#158916)
Yeah, once you get over the learning curve for ca65's advanced features, it becomes pretty apparenty just how powerful it is. ;)

Movax12 took a crack at making a "high level assembly" language out of macros for ca65: http://forums.nesdev.com/viewtopic.php?t=9272 / http://www.romhacking.net/documents/635/
Re: How do I do in CA65 things I do in ASM6?
by on (#158918)
rainwarrior wrote:
Yeah, once you get over the learning curve for ca65's advanced features, it becomes pretty apparenty just how powerful it is. ;)

Indeed. I'm still struggling a bit, but the time I spent working on these macros was essential for me to understand the basics. I won't lie though, I can't wait to finish these support macros so I can get back to coding 6502 assembly!

Quote:
Movax12 took a crack at making a "high level assembly" language out of macros for ca65: http://forums.nesdev.com/viewtopic.php?t=9272 / http://www.romhacking.net/documents/635/

I vaguely remember that, but since I didn't use ca65 at the time, I didn't pay much attention. Only now I'm catching up with the crazy stuff people have used this assembler for, like tepples reimplementing 6502 or blargg implementing Z80.
Re: How do I do in CA65 things I do in ASM6?
by on (#158931)
Some documentation for the "highlevel macros" https://www.assembla.com/spaces/ca65hl/wiki

As far as reserving RAM, you can use .struct with macros to define your variables yourself, as constants at assemble time. This is not the intended use, but I do it.

Code:
resZP baz .word

; or, multiple declarations at once

resZP {   /
foo .byte,    /
bar .byte 4   /
}


Same thing for resBSS.

The downside of keeping track of RAM at assemble is that it won't work for multiple modules. So, at the end of the file I (hackily) output the current RAM usage to a file named for the module(s) that will be called from that file. The module loads its RAM offsets based on that file. That way, exclusive sections/states of code, that will never run at the same time, can use the same memory.

Hope that makes sense. I've been thinking about a way to make the assembler do this natively.
Re: How do I do in CA65 things I do in ASM6?
by on (#159045)
Movax12 wrote:
Hope that makes sense.

Actually... I'm pretty confused! I couldn't find anything about resZP or resBSS, not in the documentation you liked to nor in the SMB High Level Disassembly files... What am I missing?

Random ca65 question time:

Code:
.macro IncludeHidden FilePath
   .scope
      .include FilePath
   .endscope
.endmacro

Why doesn't this work?

Scoping an include normally works as expected (all identifiers are hidden), but not inside a macro, and I can't for the life of me understand why.
Re: How do I do in CA65 things I do in ASM6?
by on (#159046)
ca65 macros are really strange. They're not a text-replacement macro like with C, they get turned into some sort of tokenized equivalent and ends up doing the substitution at some weird intermediate stage with a lot of funny rules. It's kind of unfortunate; I think there's equal probability that it's just a bug, or it was just somehow really hard to do scope + include inside a macro because of the internal implementation.
Re: How do I do in CA65 things I do in ASM6?
by on (#159047)
I see... so the scope is probably being opened and closed before the include itself is executed, and it affects only identifiers created inside the macro itself (none, in this case)... bummer. Simple text-replacement would've been way more intuitive.

This would've helped me make my main file look cleaner, but now I'll have to .scope the shit out of it. :cry:

I guess I could maybe cheat and have this macro include files containing ".scope" and ".endscope" instead of using the actual commands in the macro, but that would be such a nasty thing to do... if it even works! EDIT: It doesn't work. I'm out of ideas.

EDIT: One more idea - I can create scoped versions of the files I need to scope, which contain nothing but the scope and an include of the original file. Still pretty crappy.
Re: How do I do in CA65 things I do in ASM6?
by on (#159054)
tokumaru wrote:
Movax12 wrote:
Hope that makes sense.

Actually... I'm pretty confused! I couldn't find anything about resZP or resBSS, not in the documentation you liked to nor in the SMB High Level Disassembly files... What am I missing?


These are separate macros just used as an example. They use .struct and track RAM usage. I was mostly trying to make the point that you can keep track of RAM yourself, but only if you have a single module.
When you use .struct you are creating constants starting at 0, .res will just mark the identifier with its address size, the linker assigns the actual address. It would be nice if there was a way to manage RAM yourself across multiple modules without having to get too crazy with linker configurations.
Re: How do I do in CA65 things I do in ASM6?
by on (#159055)
The .scope/.include thing seems like a bug to me, assuming that by "doesn't work" you meant that it executes but the identifiers appear in global scope.

There's an issue tracker over at https://github.com/cc65/cc65/issues
Re: How do I do in CA65 things I do in ASM6?
by on (#159056)
tokumaru wrote:
I see... so the scope is probably being opened and closed before the include itself is executed


I think this is basically what is happening.
Re: How do I do in CA65 things I do in ASM6?
by on (#159057)
thefox wrote:
The .scope/.include thing seems like a bug to me, assuming that by "doesn't work" you meant that it executes but the identifiers appear in global scope.

Yes, that's what happens.

Quote:
There's an issue tracker over at https://github.com/cc65/cc65/issues

I'm not really sure about how these things work, since I've never filed but reports before (I don't even have a GitHub account). Does anyone here feel like reporting this?
Re: How do I do in CA65 things I do in ASM6?
by on (#159127)
tokumaru wrote:
(I don't even have a GitHub account)

I just tried to sign up and found out that someone had already done it using my email address somehow, and I'm pretty sure it wasn't me. The account appears to have never been used, so I stole it for me and created a new issue.

Anyway, if this macro behavior is by design (which would be pretty weird), at least it would be good to have confirmation on this.
Re: How do I do in CA65 things I do in ASM6?
by on (#159130)
BTW, I have another dilemma in my hands right now, regarding subroutines. This isn't as much of a headache as the previous problems, but I'd still like your input on this.

.proc is cool and all, but it has a serious problem that prevent me from using it for some of my subroutines. Since using .proc is equivalent to defining a label and immediately starting a scope with the same name, this means that all the code for the subroutine must be AFTER its entry point. However, sometimes I optimize loops by branching to labels BEFORE the subroutine's entry point, like in my "Wait16Cycles" subroutine (BTW, the 6 cycles from the jsr count as part of the first 16 cycles):

Code:
WasteTime: ;<- This is not an entry point!

   ;waste time (11 cycles)
   pha
   pla
   nop
   nop

Wait16Cycles:

   ;decrement the counter (2 cycles)
   dex

   ;decide between wasting more time (3 cycles) or returning (2 cycles)
   bne WasteTime

   ;return (6 cycles)
   rts

In some cases you can design the subroutine differently and branch to after the rts, or find another way to have the entry point at the very top, but in some cases, you really do need the entry point in the middle, or multiple entry points.

What I'm currently doing is this:

Code:
   .scope Wait16Cycles

WasteTime:

   ;waste time (11 cycles)
   pha
   pla
   nop
   nop

Entry:

   ;decrement the counter (2 cycles)
   dex

   ;decide between wasting more time (3 cycles) or returning (2 cycles)
   bne WasteTime

   ;return (6 cycles)
   rts

   .endscope

   Wait16Cycles = Wait16Cycles::Entry

Which is not exactly pretty, but works. I also have to create global aliases in the end for any additional entry points and for scratchpad variables defined inside the subroutine's scope if code on the outside ever uses them for passing arguments to the subroutine.

What do you guys think about this?
Re: How do I do in CA65 things I do in ASM6?
by on (#159136)
Eh, I generally only ever used scope to solve name conflict issues that already existed when trying to modify stuff I'd already written. For new code I don't tend to need scope at all, and only occasionally want to use proc, though I make heavy use of local labels (@/:).

Like, it's a useful feature when I need it, but I don't go out of my way to put everything in its own scope, etc. If proc isn't really appropriate for the way you use your subroutines, why use it at all for those ones? You're going out of your way to put a simple inside a scope, and then doing more work to take it out of that scope at the end.

Hmm, actually I'm wondering, do you know about local labels? They're basically the reason I don't really find much need for scope/proc.
Re: How do I do in CA65 things I do in ASM6?
by on (#159141)
rainwarrior wrote:
do you know about local labels?

I do, and I make heavy use of them, but they don't solve everything. Unmanned labels make the code unreadable and harder to maintain when used for larger branches/jumps, so I only use them for very localized stuff. Cheap local labels are good, but when you have multiple (global) entry points, access to cheap local labels becomes limited within the subroutine. To solve these cases I'd still need aliases, so instead of going all Frankenstein and coding each subroutine using a different methodology, I though it was better to go with the solution that works for all cases and be consistent. I find it easier to program if I decide on a single style because I start coding in that style without giving much thought to it, while using different styles depending on the situation would require more analysis each time.

Scopes seemed like a good way to isolate the subroutines, except that since you can't forward-reference scopes, You need these kinds of aliases to make the subroutine and the parts of it that need to be accessed from the outside visible.
Re: How do I do in CA65 things I do in ASM6?
by on (#159150)
tokumaru wrote:
Which is not exactly pretty, but works. I also have to create global aliases in the end for any additional entry points and for scratchpad variables defined inside the subroutine's scope if code on the outside ever uses them for passing arguments to the subroutine.

Depending on your exact use case, you may be able to use something like this for ".proc with an entry point in the middle":
Code:
.scope foo
    nop
    nop

::foo:
    nop
    nop
    rts
.endscope
bar := foo ; Just showing that "foo" is available in global scope now.

There's no way to reference the parent scope though (only global scope), so this won't work the same as .proc with nested scopes.
Re: How do I do in CA65 things I do in ASM6?
by on (#159153)
Oh, I don't know why I didn't think of that, considering that I have created labels like that in other situations. Thanks for the idea.
Re: How do I do in CA65 things I do in ASM6?
by on (#159733)
Just found another very bizarre bug: https://github.com/cc65/cc65/issues/235

I was using symbols to keep track of what macros were doing, but everything broke when I started using those macros inside scopes... Turns out that expressions don't look for symbols in outer scopes. In this particular case, it seems I can make the symbols global and access them with ::, but that definitely sounds like a bug to me.
Re: How do I do in CA65 things I do in ASM6?
by on (#159734)
It doesn't know that you aren't going to redefine (and shadow) Symbol later in the same scope. Perhaps a better rule would be the one fo-fo suggested in a reply: use in a constant expression forbids shadowing of the same variable name later in any enclosing scope.
Re: How do I do in CA65 things I do in ASM6?
by on (#159738)
tepples wrote:
It doesn't know that you aren't going to redefine (and shadow) Symbol later in the same scope.

Yeah, I figured that much. It looks pretty inconsistent though, when you reference it just fine in a instruction and in the next line it fails as part of an expression. I understand WHY it happens, but it's weird.

Quote:
Perhaps a better rule would be the one fo-fo suggested in a reply: use in a constant expression forbids shadowing of the same variable name later in any enclosing scope.

Yeah, that would probably be the best way to handle this.
Re: How do I do in CA65 things I do in ASM6?
by on (#159741)
tokumaru wrote:
tepples wrote:
It doesn't know that you aren't going to redefine (and shadow) Symbol later in the same scope.

Yeah, I figured that much. It looks pretty inconsistent though, when you reference it just fine in a instruction

Expressions can be evaluated later, even up to link time, so long as the expression is not in a context that theoretically has a way of producing a variable number of bytes. This means no conditional assembly (.if argument), no variable repetition (.repeat argument), and no variable length binary to decimal conversion (.sprintf argument).
Re: How do I do in CA65 things I do in ASM6?
by on (#159812)
Quick update on my variable declaration issues: I completely stopped mucking around with overlapping segments, dummy segments, and all that crap, because I though my macros were weird as fuck, and my config file wasn't exactly pretty either. I could barely understand them after a while. Turns out I could do everything I wanted simply by marking the start of the segments with labels, calculating all offsets with numeric variables, and creating variables by adding the offsets to the marker labels. Now I have a better understanding of what can and can't be done in a single-pass assembler, so I was able to plan things better.
Re: How do I do in CA65 things I do in ASM6?
by on (#159834)
tokumaru wrote:
I was using symbols to keep track of what macros were doing...it seems I can make the symbols global and access them with ::, but that definitely sounds like a bug to me.


Not sure if this was mentioned exactly: You can create scopes that are for your macro and related macros, like:

Code:
.scope mysupermacrovars
    foo .set 0
    bar .set 1
.endscope

.macro mysupermacro

  .if mysupermacrovars::foo
       ; do this
   .endif

.endmacro


Anything that is not explicitly scoped will be inside the same scope as where the macro was called/expanded from.
Re: How do I do in CA65 things I do in ASM6?
by on (#159913)
Yeah, I got the hang of how scopes work now. My main problem was figuring out the details of the kind of structure I wanted my program to adhere to, and in the process I kept thinking about all the things I would ever want to do, borrowing ideas from high-level programming languages and object-oriented programming. I love all the freedom you have when coding in ASM, but if you don't create a set of rules to make everything consistent, you can get lost pretty easily when working on bigger projects.

Sometimes a specific solution looks so clean and concise, almost perfect, but then you think of that one case where something will go wrong if you do it that way, so you gotta go back to the drawing board. I'm all set now though, and I'm pretty comfortable with ca65 now after having played a bit with it and understanding what it can and can't do.
Re: How do I do in CA65 things I do in ASM6?
by on (#207726)
It's been a couple years since I started this thread, and since I'm much more familiar with ca65 now than I was back then, I decided to revisit some of the ideas presented here.

The one thing I have in mind right now is how to automatically align the end portion of a PRG-ROM bank to the end of the bank, so that interrupt vectors, reset stub, trampoline routines, etc. are placed at the same position in every bank (simulating a fixed bank on mappers that don't have one) without wasting any space or needing manual tweaking of addresses or padding.

So far I've been using rainwarrior's suggestion of using two segments and padding the first one according to how much I've used of both, pushing the second one up. The main problem with this is that the amount of used space can only be calculated based on the relative distance between two labels, so the whole bank must be assembled as a single module.

Then there was this post from thefox, which I didn't understand at the time, but now I realize I can use a similar trick to have the linker automatically pad the lower part of the bank:

Code:
MEMORY {
   # this outputs the upper part and calculates its size
   FIXED: start = $0000, size = $8000, file = "fixed.bin", fill = no, define = yes;
   # this provides the correct PC for the upper part
   DUMMY: start = $10000 - __FIXED_LAST__, size = $8000, file = "", fill = no;
   # this outputs the lower portion and pads it as necessary
   BANK: start = $8000, size = $8000 - __FIXED_LAST__, file = "bank.bin", fill = yes;
}
Code:
SEGMENTS {
   BANK: load = BANK, run = BANK, type = ro;
   FIXED: load = FIXED, run = DUMMY, type = ro;
}

This works fine, the only catch is that since the fixed area has to be output first so we know its size, we have to output the two parts to separate files and concatenate them later (using copy /b or whatever) to put everything in the correct order. Of course I would rather have the assembler output the final ROM directly, instead of dealing with 32 temporary files for 16 PRG-ROM banks, so I decided to run this by you guys to see if there's anything I'm missing, or if this is indeed as close as it gets to "automatic right justifying".

I still don't understand why we can't use the symbols created by define = yes in the SEGMENTS section... That would definitely make things easier.