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 »

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.
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 »

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

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

	.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?
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 »

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

.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.
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 »

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.
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 »

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.
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 »

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

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).
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 »

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.
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 »

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

.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.
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 »

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.
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 »

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

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

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