Page 5 of 6

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

Posted: Mon Nov 09, 2015 8:57 pm
by rainwarrior
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: Select all

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

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

Posted: Tue Nov 10, 2015 5:27 am
by thefox
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: Select all

lda z:foo ; OK
foo = $55
lda z:bar ; error
bar = $123

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

Posted: Tue Nov 10, 2015 9:35 am
by tokumaru
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: Select all

.segment "ZEROPAGE": zeropage
Scratchpad: .res 32 ;reserve 32 bytes for scratchpad variables
Then you need a couple of macros:

Code: Select all

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

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?

Posted: Wed Nov 11, 2015 9:50 am
by tokumaru
OK, I believe these are the final macros for creating scratchpad variables (please don't mind my long macro names):

Code: Select all

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

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

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

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?

Posted: Wed Nov 11, 2015 12:03 pm
by tokumaru
Here's what I believe to be the final scratchpad RAM macros, in case anyone is interested:

Code: Select all

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

Code: Select all

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

;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?

Posted: Wed Nov 11, 2015 1:22 pm
by rainwarrior
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: viewtopic.php?t=9272 / http://www.romhacking.net/documents/635/

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

Posted: Wed Nov 11, 2015 1:47 pm
by tokumaru
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!
Movax12 took a crack at making a "high level assembly" language out of macros for ca65: 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?

Posted: Wed Nov 11, 2015 3:02 pm
by Movax12
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: Select all

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?

Posted: Thu Nov 12, 2015 9:44 pm
by tokumaru
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: Select all

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

Posted: Thu Nov 12, 2015 9:58 pm
by rainwarrior
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?

Posted: Thu Nov 12, 2015 10:25 pm
by tokumaru
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?

Posted: Fri Nov 13, 2015 4:34 am
by Movax12
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?

Posted: Fri Nov 13, 2015 5:08 am
by thefox
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?

Posted: Fri Nov 13, 2015 5:13 am
by Movax12
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?

Posted: Fri Nov 13, 2015 5:49 am
by tokumaru
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.
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?