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

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

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

Post by rainwarrior »

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.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

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

Post by thefox »

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.)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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.
It also requires you to put "align" in the segment definition too, I think? (see docs)
Apparently.
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.
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.
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.
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.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

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

Post by thefox »

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.
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: Select all

.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: Select all

.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.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

thefox wrote:.define is the only reasonable choice here.
OK.
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.
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.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

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

Post by thefox »

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: Select all

.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.)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
Movax12
Posts: 541
Joined: Sun Jan 02, 2011 11:50 am

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

Post by Movax12 »

thefox wrote: Here's one option:

Code: Select all

.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: Select all

.struct foo
  .res 10 ; or whatever other size, or expression you want here (if it is constant at assemble time)
  bar .byte
  ; ...
.endstruct
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

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

Post by rainwarrior »

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
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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

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

Post by tepples »

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: Select all

.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
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

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

Post by tokumaru »

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