Alternative assembler recommendations?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Alternative assembler recommendations?
by on (#196201)
Currently my SNES framework is using WLA-DX as its assembler. Technically there isn't anything wrong with this assembler that is straight up preventing me from finishing the framework, but it feels messy and I've had to deal with several workarounds to bugs in its functionality:

- Macros are fragile and inflexible ("invalid use of modulo" bug - I have to make separate versions of the same macro for immediate values and absolute addressing as the arguments).
- Potentially useful functions like RAMSECTIONs don't work due to bugs.
- Seriously, what the hell is a "slot"? This concept is not adequately explained.
- I don't trust it to generate code that won't hit a BRK and thus crash the game.

Since this is a framework I'd like others to be able to use, I'd prefer to have a cleaner codebase than I have now. I would like to know if there are any other assemblers that would fit my needs better. I would also prefer to be able to port my project over to the new assembler relatively easily. Alternatively, this topic can serve to better educate me on anything I might be doing wrong with my existing assembler.

Here's what I'm looking for:

- Fully featured 65816 assembler, generates correct code (duh)
- Ability to include source or binary files inline. I'd like to have human-readable definition files for some of my data.
- Ability to define RAM variables in separate files but not have to manage their starting addresses myself across these files.
- More reliable and readable macros. Additionally, syntax that would make it clear that something is a macro and not a subroutine.
- Handles ROM headers correctly. I don't know what the best practices are.
- Supports a FastROM + HiROM configuration.
- Ability to detect certain programmer mistakes at assemble time would be very nice - specifically calling a subroutine that assumes 16-bit registers when they're currently 8-bit.
- Documentation that isn't a single mile-long text file would be nice.
- Ability to see free space remaining in banks (like WLA already does) would be nice.

Suggestions very much appreciated!
Re: Alternative assembler recommendations?
by on (#196202)
I think ca65 fits most, if not all, of the criteria. Would you mind posting your source code?
Re: Alternative assembler recommendations?
by on (#196203)
Those points vs ca65:
HihiDanni wrote:
- Fully featured 65816 assembler, generates correct code (duh)
- Ability to include source or binary files inline. I'd like to have human-readable definition files for some of my data.
- Ability to define RAM variables in separate files but not have to manage their starting addresses myself across these files.
- More reliable and readable macros. Additionally, syntax that would make it clear that something is a macro and not a subroutine.
- Handles ROM headers correctly. I don't know what the best practices are.
- Supports a FastROM + HiROM configuration.
- Ability to detect certain programmer mistakes at assemble time would be very nice - specifically calling a subroutine that assumes 16-bit registers when they're currently 8-bit.
- Documentation that isn't a single mile-long text file would be nice.
- Ability to see free space remaining in banks (like WLA already does) would be nice.

- AFAIK it generates correct code but it's also actively maintained, so bugs can get fixed if you find any
- .include and .incbin
- .res and linker .segment will lay out your RAM variables, .import / .export / .global will let you share them between files
- the macro system is pretty good, I think, though not quite as good as C preprocessor macros
- you can make a header segment easily, though I think you'll need to generate a checksum with another tool
- the linker is very configurable, so you can lay out the ROM in any arrangement fairly easily
- i think you could create assert macros for this?
- its documentation is HTML, with a hyperlinked table of contents: http://cc65.github.io/doc/ca65.html
- the linker can generate "map" files that give you information like that
Re: Alternative assembler recommendations?
by on (#196207)
I might as well at least mention my assembler asm16. I'd actually recommend you go ca65 since it would be easier to port WLA code to it but this is a good time for me to see what people want that asm16 may lack.

Quote:
- Fully featured 65816 assembler, generates correct code (duh)
- Ability to include source or binary files inline. I'd like to have human-readable definition files for some of my data.
- Ability to define RAM variables in separate files but not have to manage their starting addresses myself across these files.
- More reliable and readable macros. Additionally, syntax that would make it clear that something is a macro and not a subroutine.
- Handles ROM headers correctly. I don't know what the best practices are.
- Supports a FastROM + HiROM configuration.
- Ability to detect certain programmer mistakes at assemble time would be very nice - specifically calling a subroutine that assumes 16-bit registers when they're currently 8-bit.
- Documentation that isn't a single mile-long text file would be nice.
- Ability to see free space remaining in banks (like WLA already does) would be nice.

- yes.
- .include and .incbin.
- unfortunately you have to use enums due to lack of a linker phase.
- macros are basically set up like in WLA, except you can pass a modulo just fine. You can also "emulate" an assembler directive by adding a period before macro calls if you really want to.
- not sure what this means. Checksum calculation? Not currently. On my mental to do list.
- yep.
- interesting feature. You can make an .if statement that leads to an .error, but subroutines would have to be called inside a macro.
- it's short and sweet.
- unfortunately not currently.
Re: Alternative assembler recommendations?
by on (#196208)
I'll take a look at both assemblers tomorrow. It's a bit frustrating that the ca65 template assumes a LoROM configuration so I might need to play around with it some.

One more useful feature that I forgot to mention: The ability to generate a symbol file for debugging purposes.
Re: Alternative assembler recommendations?
by on (#196209)
There was some (of mostly my) planning for a standard assembler for things like this,

My planned feature set was similar to yours, a 6502/65c02/Hu6280/65816/SPC700 multi-assembler with clean 3-part build process similar to ASM6, but unlike ASM6, it was to add Thingy "Table File" support for text,

Thingy was first used as a rom hacking tool for script and text editing, Byuu decided to add it in BASS (a.k.a. XKAS 14) for text support, Any other assembler did not implement this (besides ASAR from SMW Central, But that's for SMW patches!)
Re: Alternative assembler recommendations?
by on (#196210)
HihiDanni wrote:
One more useful feature that I forgot to mention: The ability to generate a symbol file for debugging purposes.

ca65's linker (ld65) can output a pretty nice symbol file with the `-Ln` option. While trying to get various mapping types correct I've also found use for the `-m` and `-vm` options (export map/verbose map file).

HihiDanni wrote:
I'll take a look at both assemblers tomorrow. It's a bit frustrating that the ca65 template assumes a LoROM configuration so I might need to play around with it some.

I was sure that some of the templates I've seen around had a Mode 21/HiROM version, but it seems both blargg's and tepples' templates use Mode 20 exclusively.

It's easy enough to edit the linker configuration for HiROM, though. Here's an example. It's really only the ROM locations in the memory map and the header/vector segments you need to change compared to a LoROM config.
Re: Alternative assembler recommendations?
by on (#196213)
You'd need to make some other slight changes to the code of my lorom-template project to get it to run in HiROM, such as not assuming that MMIO ports associated with the B Bus ($2100-$2183) and memory controller ($4200-$437F) are within the current data bank.


EDIT: clarified which "the code"
Re: Alternative assembler recommendations?
by on (#196215)
The game already runs at HiROM, unless you're talking about assembler differences here.

Anyway, I am already seeing two ca65 techniques that I could really use that I hadn't even touched on previously: ability to define stack size within the memory map definition, and the ability to export symbols to other files (which I'm 95% sure WLA can do, but as I said earlier, the readme is kind of a mess to look through).

Currently I'm designing my WRAM layout using an ODS spreadsheet for planning and .enums in code to define them in the program. Obviously I think this approach really sucks and is probably the biggest thing I'd like to improve.
Re: Alternative assembler recommendations?
by on (#196230)
I tried modifying the basic LoROM template on the wiki to be HiROM like the linked example, but I get a range error where the vectors are defined. Specifically, it seems to take issue to the reset symbol:

Code:
    .word $ff, $ff, 0, 0, 0, 0, 0, 0
    .word $ff, $ff, 0, 0, 0, 0, reset, 0


I'm guessing reset is being written as a 24-bit address because ROM0 is technically at $c00000 in the configuration? Some assistance getting a proper linker memory configuration would be appreciated. I want to have code run in LoROM banks (so $008000 at reset, then $808000 when FastROM is engaged), but data be in HiROM banks. How would I achieve this in ld65? I tried modifying the configuration somewhat to have an additional region named "LOROM" starting at $8000. If I do this, the linker "succeeds". however it seems like any code that is inserted pushes the header downward (and also expands the file).

Here is my current ld65 config, which is probably wrong in more ways than one:

Code:
# ca65 linker config for 256K HiROM
# Based on configurations by blargg and David Lindecrantz

SYMBOLS {
    __STACKSIZE__: type = weak, value = $100;
}

# Physical areas of memory
# Names need not match, but it makes it easier to remember if they do.
MEMORY {
    ZEROPAGE:   start =      0, size =  $100;
    LORAM:      start = $000100, size = $1f00 - __STACKSIZE__, define = yes;
    STACK:      start = $002000 - __STACKSIZE__, size = __STACKSIZE__, define = yes;
    HIRAM:      start = $7e2000,    size = $e000, define = yes;
    EXRAM:      start = $7f0000,    size = $10000, define = yes;
   
    LOROM:      start = $8000,      size = $8000;
    ROM0:       start = $c00000,    size = $ffb0, fill = yes, fillval = $00;
    ROMHEADER:  start = $c0ffb0,    size = $50, fill = yes, fillval = $00;
    ROM1:       start = $c10000,    size = $10000, fill = yes, fillval = $00;
    ROM2:       start = $c20000,    size = $10000, fill = yes, fillval = $00;
    ROM3:       start = $c30000,    size = $10000, fill = yes, fillval = $00;
}

# Logical areas code/data can be put into.
SEGMENTS {
    ZEROPAGE:   load = ZEROPAGE,    type = zp,  optional = yes;
    BSS:        load = LORAM,       type = bss, optional = yes;
    LORAM:      load = LORAM,       type = bss, optional = yes;
    HIRAM:      load = HIRAM,       type = bss, optional = yes;
    EXRAM:      load = EXRAM,       type = bss, optional = yes;

    CODE:       load = LOROM,       type = ro, optional = yes;
    RODATA:     load = ROM0,        type = ro;
    HEADER:     load = ROMHEADER,   type = ro, start = $c0ffb0;

    ROM1:       load = ROM1,        type = ro, optional = yes;
    ROM2:       load = ROM2,        type = ro, optional = yes;
    ROM3:       load = ROM3,        type = ro, optional = yes;
}
Re: Alternative assembler recommendations?
by on (#196231)
..., reset - $C00000, ...
Re: Alternative assembler recommendations?
by on (#196235)
HihiDanni wrote:
I tried modifying the basic LoROM template on the wiki to be HiROM like the linked example, but I get a range error where the vectors are defined. Specifically, it seems to take issue to the reset symbol:

Code:
    .word $ff, $ff, 0, 0, 0, 0, 0, 0
    .word $ff, $ff, 0, 0, 0, 0, reset, 0


I haven't used ca65 for SNES development, but I think you want to use .addr, not .word.

Quote:
Here is my current ld65 config, which is probably wrong in more ways than one:

Code:
# ca65 linker config for 256K HiROM
# Based on configurations by blargg and David Lindecrantz

SYMBOLS {
    __STACKSIZE__: type = weak, value = $100;
}

# Physical areas of memory
# Names need not match, but it makes it easier to remember if they do.
MEMORY {
    ZEROPAGE:   start =      0, size =  $100;
    LORAM:      start = $000100, size = $1f00 - __STACKSIZE__, define = yes;
    STACK:      start = $002000 - __STACKSIZE__, size = __STACKSIZE__, define = yes;
    HIRAM:      start = $7e2000,    size = $e000, define = yes;
    EXRAM:      start = $7f0000,    size = $10000, define = yes;
   
    LOROM:      start = $8000,      size = $8000;
    ROM0:       start = $c00000,    size = $ffb0, fill = yes, fillval = $00;
    ROMHEADER:  start = $c0ffb0,    size = $50, fill = yes, fillval = $00;
    ROM1:       start = $c10000,    size = $10000, fill = yes, fillval = $00;
    ROM2:       start = $c20000,    size = $10000, fill = yes, fillval = $00;
    ROM3:       start = $c30000,    size = $10000, fill = yes, fillval = $00;
}

# Logical areas code/data can be put into.
SEGMENTS {
    ZEROPAGE:   load = ZEROPAGE,    type = zp,  optional = yes;
    BSS:        load = LORAM,       type = bss, optional = yes;
    LORAM:      load = LORAM,       type = bss, optional = yes;
    HIRAM:      load = HIRAM,       type = bss, optional = yes;
    EXRAM:      load = EXRAM,       type = bss, optional = yes;

    CODE:       load = LOROM,       type = ro, optional = yes;
    RODATA:     load = ROM0,        type = ro;
    HEADER:     load = ROMHEADER,   type = ro, start = $c0ffb0;

    ROM1:       load = ROM1,        type = ro, optional = yes;
    ROM2:       load = ROM2,        type = ro, optional = yes;
    ROM3:       load = ROM3,        type = ro, optional = yes;
}

First of all, all RAM regions in the MEMORY section need to have file = "" or else they'll get output to the ROM file (and push everything else out of place). Second, regions that you do want in the ROM file need to be listed in the order you want them written to the ROM, not necessarily the order that they appear in the CPU address space. And everything that gets written to the ROM needs fill = yes so that banks stay aligned.

If you want data in the lower half of each 64K bank and code in the upper half (like Super Robot Wars 4) you need something like this:

Code:
    DATA0:      start = $c00000,    size = $8000, fill = yes, fillval = $00;
    CODE0:      start = $808000,    size = $7fb0, fill = yes, fillval = $00;
    ROMHEADER:  start = $80ffb0,    size = $50,   fill = yes, fillval = $00;

    # repeat for however many code+data banks you need:
    DATA1:      start = $c10000,    size = $8000, fill = yes, fillval = $00;
    CODE1:      start = $818000,    size = $8000, fill = yes, fillval = $00;
    DATA2:      start = $c20000,    size = $8000, fill = yes, fillval = $00;
    CODE2:      start = $828000,    size = $8000, fill = yes, fillval = $00;

    # finally, for HiROM I think data-only banks can all go in one big region,
    # since they're contiguous both in ROM and in CPU address space
    # not 100% sure about this though (ld65 might not allow regions larger than 64K)
    BIGDATA:  start = $cX0000,    size = $XXXXX, fill = yes, fillval = $00;
Re: Alternative assembler recommendations?
by on (#196236)
AWJ wrote:
I haven't used ca65 for SNES development, but I think you want to use .addr, not .word.

It's an interrupt vector, they must specifically be word-sized, unless you know of a way to force it to use just the lower 16 bits. Otherwise I might use tepples' suggestion despite looking kind of hackish.
Re: Alternative assembler recommendations?
by on (#196237)
HihiDanni wrote:
AWJ wrote:
I haven't used ca65 for SNES development, but I think you want to use .addr, not .word.

It's an interrupt vector, they must specifically be word-sized, unless you know of a way to force it to use just the lower 16 bits. Otherwise I might use tepples' suggestion despite looking kind of hackish.


.addr does force the output to be word-sized. For a 24-bit pointer you use .faraddr.
Re: Alternative assembler recommendations?
by on (#196239)
I use the ".loword" directive to get the correct bytes into the vector tables, like so.

According to the manual ".addr" is an alias for ".word", so if true using it should yield the same overflow error.
Re: Alternative assembler recommendations?
by on (#196240)
Optiroc wrote:
According to the manual ".addr" is an alias for ".word", so if true using it should yield the same overflow error.

Only for 6502, it says.
Re: Alternative assembler recommendations?
by on (#196242)
Nicole wrote:
Optiroc wrote:
According to the manual ".addr" is an alias for ".word", so if true using it should yield the same overflow error.

Only for 6502, it says.

:oops: I should be more careful about posting two minutes after being forced out of bed...
Re: Alternative assembler recommendations?
by on (#196243)
Quote:
- Fully featured 65816 assembler, generates correct code (duh)
- Ability to include source or binary files inline. I'd like to have human-readable definition files for some of my data.
- Ability to define RAM variables in separate files but not have to manage their starting addresses myself across these files.
- More reliable and readable macros. Additionally, syntax that would make it clear that something is a macro and not a subroutine.
- Handles ROM headers correctly. I don't know what the best practices are.
- Supports a FastROM + HiROM configuration.
- Ability to detect certain programmer mistakes at assemble time would be very nice - specifically calling a subroutine that assumes 16-bit registers when they're currently 8-bit.
- Documentation that isn't a single mile-long text file would be nice.
- Ability to see free space remaining in banks (like WLA already does) would be nice.


64TASS
It has full 65816 support ( + 6502, 65C02, R65C02,W65C02,65CE02, 64dtv02, 65el02,6510, 4510 - I've asked about Hu6280 which sadly has 4 char opcodes making it harder to support at the moment ). All opcodes are supported, it will determine and work out from.databank and .dpage settings which is the correct addressing mode. It also has manual sizing and auto sizing. I have requested the ability to make auto collapsing REP/SEP statements, especially for macros. With these the assemblers build in optimiser will be able to determine if a REP/SEP is not needed and remove it automatically, so you can set them in Macros and not worry about them wasting space/clocks, Soci(the developer) has agreed and wants it, but it a little down the list at the moment. I have also added a request for special warnings that let me check that the size of a,index in code to catch such errors.

.include <- bring in a file whole
.binclude <- put everything in a file in a block basically lets you pack a file into a namespace
.binary <- brings in binary data, which you can skip bytes and only take a certian number of ie. .binary "myFile.bin",2,100 skips first 2 and brings in 100 bytes
worth noting, you can union a binary file with structures to get you labels for things in the file if you wish

.section can be added to from anywhere, and then instantiated in a location eg.
Code:
*=$00
.dsection zp
then elsewhere you can do

myFun
.section zp
localVar .byte ?
localWord .word ?
.send zo
this will make variables in the myFun name space but they will be collected and declared in the ZeroPage area. Upon assembly it will show the size of each segment e.g.
Code:
Memory range:      $0801-$13b3   $0bb3
Section:           $0002-$0064   $0063   zp
Section:           $0866-$09a4   $013f   sBitmapDepack
Memory range:      $0866-$09a4   $013f
Section:           $09a5-$13b3   $0a0f   sDuckBitmapData
Memory range:      $09a5-$13b3   $0a0f


Macros, Functions and Segments are all well accounted for. You can call eitther of them with a '.' or '#' prefix, personally I force upon my self . for function and # for macro/segment. As to what is the difference, this is a function
Code:
fCalcBitmapAddrChar .function base,x,y
.endf base+(y*320)+(x*8)
which makes data at assemble time and returns the address/param to be assembled. Macros have their own namespace so any sub label defined in the macro is safe, it won't be duplicated if you put 2 in the same namespace. But it won't be able to nativly handle other labels in the instigators namespace, segments put everything in the instigators namespace.

No build in SNES header support, so it would need a macro built, the assembler has basically python levels of slicing built in, so a simple data structure could be made which a macro then spits out and converts to data in a human readable format.
With Logical, Base, Full bank support and blocks, sections it would be easy to build a full SNES ROM with it. I've used it to build a full D64 image, SNES should easily be doable. As the assembler supports atari xex and Apple][ formats, if enough of us want a SNES header, soci would probably put it in ;)

64Tass has a variety of warnings, the best one for me is the "Did you forget to add a # for this LDA $40?", it also has page boundary crossing warnings/errors. The Optimiser ( not sure how 65816 compat it is though ) will detect other silly things, like no need to clc here, this jump can be a BNE etc. It also has build in "alias" that will chose the minimal jump you need for a sitation for 65816 the GRA opcode will translate to BRA if within range auto changing to JMP XXXX if needed or JMP XXXXXX if needed. there are GNE,GEQ,GCC... it also covers the standards, code wrap at end of 64K, multi byte char, float compare, sign mismatch on .byte, .word etc

Sadly giant monolith help doc, hyper linked though http://tass64.sourceforge.net/

if one makes each bank a section then it will show the amount used, one could use a .warn "bank " + ^* + " has " + $FFFF-* + " bytes free" to get free though.

The ability to generate a symbol file for debugging purposes.
It has 2 output formats, its own and VICE format. These are basically label name,address, it will append all sub children, local variables, members of structs, blocks, sections etc

Also worth noting it is N pass, currently I use 7 passes in my code, but it scales as needed.

It is very actively maintained and is open source, and is so pure C it will even compile for 68K on an A500.
It is also has full backwards compatibility with TASS/TASM, not that is should be an issue around here ;)
Re: Alternative assembler recommendations?
by on (#196283)
Wow, I just skimmed through the documentation and have to say that there's certainly much to love about 64tass. I'll have to play around with it a bit for SNES use.

Really, the only thing I like about ca65 is that there's as of yet nothing I could think of that its macro language can't accomplish – but it's more through grunt and white knuckles than elegance you get things done with it a lot of the time. I do like the linker and memory configuration features, though.
Re: Alternative assembler recommendations?
by on (#196287)
Optiroc wrote:
I use the ".loword" directive to get the correct bytes into the vector tables, like so.


Aha! .loword is used as function syntax. Thanks for the help with the linker configuration as well - it looks like I'm set to go ahead and start porting the actual code now!
Re: Alternative assembler recommendations?
by on (#196305)
Quote:
Really, the only thing I like about ca65 is that there's as of yet nothing I could think of that its macro language can't accomplish

I have one (do chime in if I'm wrong): its C-like define cannot take more than one parameter, and the normal macro cannot be used for partial things (parameters to instructions), only full things with instructions.

That means I couldn't make a NTADR_A(x, y) macro, and instead had to make a "NTLINE_A(y) | x" workaround.
Re: Alternative assembler recommendations?
by on (#196311)
calima wrote:
Quote:
Really, the only thing I like about ca65 is that there's as of yet nothing I could think of that its macro language can't accomplish

I have one (do chime in if I'm wrong): its C-like define cannot take more than one parameter, and the normal macro cannot be used for partial things (parameters to instructions), only full things with instructions.

That means I couldn't make a NTADR_A(x, y) macro, and instead had to make a "NTLINE_A(y) | x" workaround.

There must have been some other bug going on, as other projects do multi-parameter defines (with the same purpose) just fine.
Re: Alternative assembler recommendations?
by on (#196322)
Yeah, the ca65 .define macros support multiple arguments just fine, however you must make sure not to add extra parenthesis when you call them:

Code:
.define foo(p1, p2) p2+p1
.byte foo 11, 22 ; ok
.byte foo(11, 22) ; not ok -- the first argument will be "(11", and then second one is "22)".


EDIT: Changed the macro expression to p2+p1 to better illustrate the point. Now it expands to "22)+(11".
Re: Alternative assembler recommendations?
by on (#196326)
Are the docs wrong?

http://cc65.github.io/doc/ca65.html#ss12.7
Quote:
Beware: Since the assembler cannot detect the end of one parameter, only the first token is used. If you don't like that, use classic macros instead:


I read that as saying .define only supports one parameter.
Re: Alternative assembler recommendations?
by on (#196327)
calima wrote:
Are the docs wrong?

http://cc65.github.io/doc/ca65.html#ss12.7
Quote:
Beware: Since the assembler cannot detect the end of one parameter, only the first token is used. If you don't like that, use classic macros instead:


I read that as saying .define only supports one parameter.

They definitely support multiple parameters (e.g. I have PPU tile address macros that take X,Y parameters). I'm very confused as to what that statement is supposed to mean.
Re: Alternative assembler recommendations?
by on (#196329)
thefox, your example confuses me because this also works without problem. (note to self: archive links)
Re: Alternative assembler recommendations?
by on (#196331)
calima wrote:
Are the docs wrong?

http://cc65.github.io/doc/ca65.html#ss12.7
Quote:
Beware: Since the assembler cannot detect the end of one parameter, only the first token is used. If you don't like that, use classic macros instead:


I read that as saying .define only supports one parameter.

I'm not sure what the docs are trying to say there, but the actual documentation for .define is clear cut: http://cc65.github.io/doc/ca65.html#.DEFINE

nicklausw wrote:
thefox, your example confuses me because this also works without problem. (note to self: archive links)

It happens to work by coincidence. NTXY is defined as .define NTXY(xc,yc) ((xc)|((yc)<<5)). Then, NTXY(0, 23) expands to (((0)|((23))<<5)), which happens to have nicely balanced parenthesis.

But you're right, my example was bad, because the "not OK" case compiled even though it's ill-advised. I swapped the parameters around to better illustrate the point.
Re: Alternative assembler recommendations?
by on (#196333)
thefox wrote:
nicklausw wrote:
thefox, your example confuses me because this also works without problem. (note to self: archive links)

It happens to work by coincidence. NTXY is defined as .define NTXY(xc,yc) ((xc)|((yc)<<5)). Then, NTXY(0, 23) expands to (((0)|((23))<<5)), which happens to have nicely balanced parenthesis.

But you're right, my example was bad, because the "not OK" case compiled even though it's ill-advised. I swapped the parameters around to better illustrate the point.

It happens to compile without error because of the balanced parentheses, but beyond that it happens to work because because "xc" is 0.

In general, parentheses + defines/macros in ca65 are a bit of a landmine, unfortunately. (See also: (indirect), Y addressing.)

I find it very strange/interesting that it obviously knows that '(' is not part of the name of the define, too, so that character has a special "new token" property to it.

Maybe we should propose some changes to the documentation just to point out how weird and error prone parentheses are when used with these features?
Re: Alternative assembler recommendations?
by on (#196339)
The confusing thing is that parentheses are used when defining but not when invoking:

Code:
.define foo(a, b, c) ; ...

; invoke
foo 0, 1, 2
Re: Alternative assembler recommendations?
by on (#196341)
rainwarrior wrote:
In general, parentheses + defines/macros in ca65 are a bit of a landmine, unfortunately. (See also: (indirect), Y addressing.)

The problem is that you aren't supposed to put parentheses around the argument list when invoking a macro (either a multiline .macro or a one-line .define). Assembler macros aren't C preprocessor macros. If you put parentheses around the argument list, the opening parenthesis gets interpreted as part of the first argument and the closing parenthesis as part of the last argument, which is almost certainly not what you want to happen.

If you want to pass an argument that contains a separator character like a comma, you put curly braces around that individual argument.

There's a clear example of this use of curly braces in the documentation (which, admittedly, is generally poorly written and unclear), under "Parametrized macros".

Quote:
I find it very strange/interesting that it obviously knows that '(' is not part of the name of the define, too, so that character has a special "new token" property to it.

The definition of a one-line macro requires parentheses around the parameter list, so that the parser knows where the list of parameters ends and where the actual definition (what to expand the macro to) begins.

I have no idea either what the sentence "Since the assembler cannot detect the end of one parameter, only the first token is used" is supposed to mean.
Re: Alternative assembler recommendations?
by on (#196748)
Alright, I have a couple questions currently:

- How do I force 24-bit addressing? I need this for the SPC upload routines in order to support uploading data from different data banks while still having access to the upload src/dest/size variables.
- How is the checksum and complement computed? This page gives a basic description but since they're part of the file being checksummed, wouldn't that mess things up? I'd think at least the complement might cause the result of the checksum to differ based on its initial value?
Re: Alternative assembler recommendations?
by on (#196752)
Checksum + inverted checksum = 0

Just set both fields to zero, checksum the file, and set the fields.
Re: Alternative assembler recommendations?
by on (#196774)
HihiDanni wrote:
- How do I force 24-bit addressing? I need this for the SPC upload routines in order to support uploading data from different data banks while still having access to the upload src/dest/size variables.

In ca65, use f:$2140. In any assembler, use $212140.

Quote:
- How is the checksum and complement computed? This page gives a basic description but since they're part of the file being checksummed, wouldn't that mess things up? I'd think at least the complement might cause the result of the checksum to differ based on its initial value?

The third byte of the checksum is always $FF minus the first byte, and The fourth byte is always $FF minus the second byte. Thus the four bytes will by definition always add to $1FE, as if the four bytes were 00 00 FF FF. So overwrite the checksum with those four bytes before calculating it.
Re: Alternative assembler recommendations?
by on (#196821)
Okay, I have successfully ported my engine over to ca65! Thanks for the help! Already this has several advantages:

- My project now has a proper structure with each source file being its own module, with all the modules linked together after assembly.
- I no longer need to maintain a spreadsheet of the WRAM layout to make sure I'm not overwriting variables from other modules
- While porting the engine to the new assembler I ended up fixing a few bugs that only cropped up due to the altered memory layout, and made the game actually run slightly faster in the process!
- I understand the memory layout as seen by the linker which makes it easier to move things around the different memory regions
- The size of the game object list can be easily changed at assembly time to be something other than 128 to adjust the tradeoff between max objects and memory usage/overhead.

However, I do have a couple small issues which I would like some advice on:

- When I ported the macros for transferring data to VRAM, etc., I made it so immediate addressing is no longer implicit (or required). However, now you have to write the macros like this:

Code:
Macro_LoadVRAM #.loword(Tileset), #<.bank(Tileset), #$0000, #$8000

Now I don't mind the need to specify # quite so much (although it's technically error-prone - if you forget the # you'll end up with different transfer addresses than intended). However, the use of .loword() and <.bank() is really ugly. Any suggestions on best practices to use here to make the syntax less painful to deal with?

- Additionally, it seems like you need to import/export each symbol or label individually, which wouldn't be so bad if I didn't also want to have things like sprite definition files with lots of labels that I really, really don't want to have to import/export one by one. That would be a nightmare. Surely there's a better way to deal with this, perhaps by grouping them so just a single import/export is needed?
Re: Alternative assembler recommendations?
by on (#196822)
Why not just pass Tileset to the macro and then inside it use .bank and .loword?

Also, .global works wonders. Cuts imports and exports in half.
Re: Alternative assembler recommendations?
by on (#196823)
Passing Tileset to the macro was the previous way of doing things. Perhaps I should make two versions of macros, make the first one always use immediates, and the second one called Indirect.

But now that I think about it, all the macros are doing are setting a few register values before jumping to the subroutine that does the DMA transfer. So I may as well just make the macros immediate-only.

I'm hesitant to use .global, and it doesn't really address my issue. Mostly I don't want to have to list each label twice in my file (once for the definition and once again for the export).
Re: Alternative assembler recommendations?
by on (#196863)
HihiDanni wrote:
However, the use of .loword() and <.bank() is really ugly. Any suggestions on best practices to use here to make the syntax less painful to deal with?

You can handle it at the macro side (where appropriate, as discussed), or .define your own macro aliases to simplify the syntax. For example:
Code:
.define lw(v) .loword(v)
.define bank(v) .lobyte(.bank(v))
Macro_LoadVRAM #lw Tileset, #bank Tileset, #$0000, #$8000

(YMMV whether this is actually any better.)

HihiDanni wrote:
Passing Tileset to the macro was the previous way of doing things. Perhaps I should make two versions of macros, make the first one always use immediates, and the second one called Indirect.

But now that I think about it, all the macros are doing are setting a few register values before jumping to the subroutine that does the DMA transfer. So I may as well just make the macros immediate-only.

Quote:
Mostly I don't want to have to list each label twice in my file (once for the definition and once again for the export).

You can use export foo := * instead of .export foo + foo:. (Or, you can make your own macro to simplify the export.)

With .global, you would have .global foo in your module header (e.g., module.inc), and the definition (e.g., module.s) would simply have foo:.
Re: Alternative assembler recommendations?
by on (#196868)
thefox wrote:
Code:
.define lw(v) .loword(v)
.define bank(v) .lobyte(.bank(v))
Macro_LoadVRAM #lw Tileset, #bank Tileset, #$0000, #$8000

(YMMV whether this is actually any better.)

It's surprisingly elegant!

Quote:
You can use export foo := * instead of .export foo + foo:. (Or, you can make your own macro to simplify the export.)

With .global, you would have .global foo in your module header (e.g., module.inc), and the definition (e.g., module.s) would simply have foo:.

What's the definition of foo here? Does foo become a namespace? In that case, how is the import done, and what's the syntax for referencing the labels from the import?
Re: Alternative assembler recommendations?
by on (#196874)
foo is just a label, not a namespace.

If you had foo.s:
Code:
.include "foo.inc"
.code
foo:


foo.inc:
Code:
.global foo


And main.s:
Code:
.include "foo.inc"
.code
main: jmp foo


This would work without error. (.code is short for .segment "CODE").

Edit, forgot to mention that you could do what thefox said and change foo: to .export foo := * and .global foo to .import foo and take out the include for foo.s and that would also work. Not sure why you'd want to do that.
Re: Alternative assembler recommendations?
by on (#196877)
The use of ".export foo := *" implies there is some kind of wildcard here, hence I assumed it to be some kind of namespace. Which is really what I want - the ability to mass export/import labels for definition files.

At this rate the best thing I can think of is to put ROM data definitions inside an include file and somehow find a way to ensure that it's only written to ROM once even if included from multiple locations.

Edit: Or better yet, a more concrete example of using ".export foo := *" with a large number of labels. I'd prefer all these labels being declared once if possible.
Re: Alternative assembler recommendations?
by on (#196879)
.scope foo creates a namespace, but not a label.
.proc foo creates both at once, a label that also encloses a namespace.
.global foo import and/or exports a symbol, it doesn't create foo by itself.
.export foo only exports a symbol, its definition will be elsewhere.
.export foo := * apparently exports and defines foo in one line (compound of assignment and export into one statement).
* is not a wildcard, it is the current PC location in the code.

Just to clarify that last one, * works like other labels, it just happens to change its value on every line:
.export foo := bar exports and defines foo as equal to another label.
.export foo = 25 exports and defines foo as the number 25.

HihiDanni wrote:
Which is really what I want - the ability to mass export/import labels for definition files.

Each label/symbol you want to import or export must be explicitly mentioned in an .import/.export/.global statement. You can't import an entire namespace in one step, by itself...

However, if you want to create groups of these, you can put a collection of .global statements in a separate "header" file and .include that header in files that need them. The include allows you to hide a bunch of code in another file via in a single line, at least?
Re: Alternative assembler recommendations?
by on (#196882)
Speaking of * and namespaces, I filed feature request #346 for prefix exports and imports.
Re: Alternative assembler recommendations?
by on (#196884)
Thanks for the clarification. Thinking about this some more, I might wrap some of this stuff into a macro which would be both human- and tool-friendly.