How do I do in CA65 things I do in ASM6?
Moderator: Moderators
Re: How do I do in CA65 things I do in ASM6?
Oh, you're right! This is really starting to make sense to me!
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: How to do in CA65 things I do in ASM6
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".
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.
Code: Select all
.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
Re: How do I do in CA65 things I do in ASM6?
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:
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.
- 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: Select all
BlockStart:
nop
nop
BlockEnd:
; Fine, the value is known at this point.
.res BlockEnd-BlockStart
Code: Select all
; Error, need to know value (affects amount of generated data)
.res BlockEnd-BlockStart
BlockStart:
nop
nop
BlockEnd:
Code: Select all
; 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:
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: How to do in CA65 things I do in ASM6
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.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.
Will keep that in mind, thanks.thefox wrote:It's a single-pass assembler
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
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.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).
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: Select all
.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
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: How to do in CA65 things I do in ASM6
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?tokumaru wrote: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.FIXED: load = PRG, type = ro, start = $FF90;
Yes. I did something like this for level data in a recent project.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?
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.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.
Code: Select all
,---------------.
| |
| A |
| |
+-------+-------+
| | |
| B | |
| | C |
`-------+ |
| |
,-------+-------+
| |
| D |
| |
`---------------'
tokumaDRW?rainwarrior wrote:These are additional constraints you're dropping on me now after the fact.
Re: How to do in CA65 things I do in ASM6
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.thefox wrote:There's an attribute "bank" that can be specified for memory areas in the linker configuration.
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.tepples wrote:Do you want me to go request this feature in ld65 on the cc65 project's issue tracker on GitHub?
Good to know that this isn't such an alien thing to do, but I'd still try to avoid it, if possible.Yes. I did something like this for level data in a recent project.Would it be acceptable for me to use hundreds of .export statements
Sounds interesting."union segments"
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!tokumaDRW?
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: How to do in CA65 things I do in ASM6
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.tokumaru wrote:Would it be acceptable for me to use hundreds of .export statements
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.
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:Do you want me to go request this feature in ld65 on the cc65 project's issue tracker on GitHub?
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.tepples wrote:"union segments"
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.tokumaru wrote:Oh man, I'll try harder to not come off like that.tepples wrote:tokumaDRW?
Re: How do I do in CA65 things I do in ASM6?
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).
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).
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: How do I do in CA65 things I do in ASM6?
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.thefox wrote:Since you can specify only one base address for the memory area, this can't be done.
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?
Thanks for the comment. I actually think it helped me figure out a good solution for this:rainwarrior wrote: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.thefox wrote:Since you can specify only one base address for the memory area, this can't be done.
Code: Select all
# 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: Select all
.segment "CODE0_8000"
.proc at8000
jmp *
.endproc
.segment "CODE0_C000"
.proc atC000
jmp *
.endproc
Code: Select all
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 ................
...
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
- rainwarrior
- Posts: 8735
- Joined: Sun Jan 22, 2012 12:03 pm
- Location: Canada
- Contact:
Re: How do I do in CA65 things I do in ASM6?
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?
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?
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.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?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: How do I do in CA65 things I do in ASM6?
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):
Any way to achieve similar behavior in ca65, without having to create hundreds of overlapping segments, one for each subroutine?
Code: Select all
.enum Scratchpad
RowPointer .dsb 2
BlockCount .dsb 1
.ende
Re: How do I do in CA65 things I do in ASM6?
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.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.
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