SPC700 assemblers overview

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
SPC700 assemblers overview
by on (#81376)
If you ever need to write some code for SNES APU, you'll need one. Unfortunately, there is very few assemblers, and they aren't easy to find. I've spent a day searching available options, and here is the results, to save your time if you'd need this.

First, there are official tools, because the CPU core in SNES APU is just a part of a large MCU series (named SPC700). As an individual enthusiast, you can't have the tools, they probably cost a lot, but anyway, just as an interesting fact - there is Sony's assembler named WASM700II (virtually not mentioned in the internet), and Gaio Technology's toolset that includes an assembler, linker, C compiler, and IDE. You can find a lot of details on the MCU series and overview of the software in this PDF, and overview of Gaio's tools on their website.

For common people, less impressive options are available:

wladx-spc700 - unlike support of other processors, SPC700 support seems bad. There are reports from people who attempted to use it that it generates wrong code and has other bugs. There are sources, though, so maybe you will be able to fix the assembler (would be cool).

Telmark Cross Assembler (TASM) - is does not have native SPC700 support, but it is table-driven. You need to use this table created by GAU and revised by eKid. It was used to compile 5000+ lines of xmsnes, so it is tested well. There are downsides, though - this assembler is just old, and it is shareware. It costs $25, according to docs 'prolonged use without registration is unethical'.

spcas - very simple assembler specifically designed for SPC700, made by byuu, author of the bsnes. Reports are that it works well, but lacks many features, so may be not suitable for large projects.


If you are an hero, there are two extra options.

First, you can use an existing macro assembler and make macros for all the opcodes. If will work, and will give you nice set of features, but syntax will be unusual, because macro assemblers usually don't like spaces and special symbols in macro names, and error messages could be confusing.

Second, you can write your own assembler. It is a large project by itself, and in short time starting from scratch you can only make something comparable with spcas. However, if you already made an assembler, adapting it to SPC700 could take not that much time and effort.

by on (#81377)
Or a way that doesn't require one to be suicidal: write a preprocessor in Perl or Python that outputs .byt statements, and have ca65 assemble that. This is like the macro way, but with the advantage of having more sophisticated syntax thanks to the regular expression facilities in such languages. It's also like the write-your-own-assembler way, but with the advantage that ca65 and ld65 handle all the label processing for you.

by on (#81378)
Agree, it could be a nice compromise between using macros and writing own assembler.

by on (#81379)
bass (successor to xkas) supports SPC700 syntax in Sony's format, and in an infinitely more logical 65xx style syntax. Your choice.

Has full define+macro+relocation support, among other things. Only thing it doesn't do is produce objects, but with a 16-bit processor and 64K RAM max, you really, really don't need that. Really.

Temporary link:
http://byuu.org/temp/bass_v00r06.tar.bz2

You'll have to compile it yourself (very easy, install TDM/GCC and run a batch file), or wait another day or two for v01 to be posted.

New syntax example:
Code:
  sep
  jst 4
  set $55.2
  bbs $55.2,label
  eor $55
  eor $55aa
  eor (x)
  eor ($55+x)
  eor #$55
  eor $55,$aa
  and $55aa.0
  lsr $55
  lsr $55aa
  phx
  trb $55aa
  jsp $55


Again: old, ugly syntax is fully supported as well.

by on (#81381)
byuu, that's great. I almost decided to make my own assembler, and this will save me a lot of time.

Edit: Unfortunately it turned out that my bass build is broken to completely unusable state, including not working defines, macros, comments, etc. spcas also does not work for me, I haven't found anything like equ, and it does not report about problems, in simplest test it ends up in 7 bytes of generated code instead of expected 11 without any warning or errors. Together with lack of debuggers it makes work on code next to impossible.

by on (#81413)
Code:
http://byuu.org/files/bass_v01.tar.bz2


Binaries and GPLv2 source included.

I can assure you that defines/macros work just fine, see example code under test/ :D

by on (#81419)
Tried this version. Defines seems working, but the same problem with macro:

Code:
macro length()
  $000000
endmacro


Results in 'unknown command:macro length()'.

Other problem is that none of possible combinations of org and base allow to create a file assembled to, say, $0200, but without $200 zeroes in beginning of the file.

by on (#81430)
Sorry, namespace.asm wasn't updated yet.
Defines and macros were merged, ala test.asm. They work like this now:

Code:
define base = $80
define add x = clc; adc.w #{base}+{x}
define sum x = {add {x}+0}; {add {x}+1}; {add {x}+2}; {add {x}+3}
define all x,multiplier
  {sum {x}+0*{multiplier}}; {sum {x}+1*{multiplier}}
  {sum {x}+2*{multiplier}}; {sum {x}+3*{multiplier}}
enddef

define hvbjoy = $4212

define loop
  wait{#}:
    lda {hvbjoy}
    bmi wait{#}
enddef

//this remaps strings for eg db "ABCD" -> db "abCD"
define 'A' = 0x61
define 'B' = 0x62

org $8000; base $7ec000
  {all $20, 4}; {all $30, 4}


Quote:
Other problem is that none of possible combinations of org and base allow to create a file assembled to, say, $0200, but without $200 zeroes in beginning of the file.


Right, org is the location in the file, base is the location the code sees.

Use org 0; base $200 for what you want.

by on (#81432)
Ok, thanks. Now the last problem: comments with ; aren't work:

Code:
rts ;test

Results in 'unknown command:test'. The same happens with any ;comment, does not matter if it is on a new line, with or without space after opcode.

by on (#81433)
Comments use //
Block separators use ;
It's like C++-style, but you don't need one for the last block on a line.

I know people like ; for comments, but ; is the most logical block separator.

Eg:
lda #$07; sta $2100 //set screen brightness to half intensity

If it bothers you that greatly, you can edit the source to change it, but please give it a chance =)

by on (#81434)
It bothers me because I use many different assemblers where a lot of things are different, and this is one extra thing. It is very uncomfortable to remember and relearn set of rules for every assembler.

Examples are # or $ for hex, @ or . or _ for local labels, : after labels in some assemblers, etc.

In many other assemblers block separator is : (and labels without : ). In 99% of assemblers ; is for comments.

I can change it in the source, of course, but what will do people who would like to use my code? Change source too, or replace ; with //? It is kinda unconvenient.

by on (#81437)
Gnu Assembler uses @ for comments, and that's just wrong on so many levels. But that's what it uses.

by on (#81439)
Well, I think I can solve this problem seamlesly with a very simple preprocessor that will replace all the ; with // before compilation, and publish preprocessed files only. Still not very convinient, but as it is seemingly the best free SPC700 assembler available (and with active author, which is important), it is tolerable.

by on (#81441)
Byuu's Macro technique is similar to TASM's Define/Macro technique,

That sounds like a decent thing.

by on (#81443)
I found it not so decent. Simplest thing from wla like

Code:
.define ADDR=$f2
.define DATA=$f3

.define DSP_FLG=$6c

.macro dspn
   mov ADDR,\1
   mov DATA,\2
.endm

...

 dspn DSP_FLG,$00


turns into

Code:
define ADDR=$f2
define DATA=$f3

define DSP_FLG=$6c

define dspn reg,val
   sti {ADDR},{reg}
   sti {DATA},{val}
enddef

..

 {dspn {DSP_FLG},$00}


In this case amount of additional symbols makes macros not helpful, because they are there to make code more readable, not more complex. Also, try to forget to use {} somewhere if you want to have a lot of fun searching what is wrong.

This system is probably works well for more complex uses, but not for this one.

by on (#81445)
Well, like I tell others, if // for comments bothers you that much, change it or use something else.

I abhor :less labels because that makes any error compile as valid code.

Code:
clx  //should've been clc, but now it's a label named clx
adc $2100


And with :, block separators on labels become unreadable:
Code:
loop: : dec : beq end : inx : bra loop : end: : rts

Code:
loop:; dec; beq end; inx; bra loop; end:; rts


Defines have both start and close markers so that they are not ambiguous with labels, opcodes and directives, and can concatenate.

Try and specify define x, then define y, then the letter z.
Code:
{x}{y}z
<- very clear purpose
Code:
!x!yz
<- !x + !yz
Code:
xyz
<- is it an opcode? A define named xyz? A label?

Otherwise, I would certainly like markerless defines. It'd basically be a true table assembler at that point.
define clc = db $18 //or whatever clc is
define adc #n = db $69,{n}
define add n = clc; adc {n}
add #$24 //clc; adc #$24

But well, whatever. The code is extremely clean. It's not at all hard to change this stuff. Given how obscure SPC700 ASM code is, just include the assembler source with your SPC700 source.

Or don't worry about minor semantics issues ;)

by on (#81449)
byuu wrote:
I abhor :less labels because that makes any error compile as valid code.

Same here.

Quote:
And with :, block separators on labels become unreadable:
Code:
loop: : dec : beq end : inx : bra loop : end: : rts

Code:
loop:; dec; beq end; inx; bra loop; end:; rts

I don't put multiple instructions on one line in my own code, instead choosing to edit in a narrower window and use a newline and two spaces between instructions. But one could decide that when the label separator and the instruction separator are set to the same, a label may appear only in the first position on a line.
Code:
loop: dec : beq end : inx : bra loop
end: rts

This would be a compromise between detecting incorrect 'clx : ' and allowing the more familiar syntax.

Quote:
Defines have both start and close markers so that they are not ambiguous with labels, opcodes and directives, and can concatenate.

Try and specify define x, then define y, then the letter z.
Code:
{x}{y}z
<- very clear purpose

Or as the C preprocessor puts it: x ## y ## z

by on (#81450)
byuu, how can you explain this?

This does not compile, gives both 'unknown token' and 'unrecognized token' errors on the line where the macro used:
Code:
define dspa reg
   sti {ADDR},#{reg}
   sta {DATA}
enddef

..

{dspa {DSP_ADSR1}}


This compiles. Obviously does not work, I just tried it at random after ran out of ideas why seemingly correct code just don't want to compile (as compiler does not provide too much info in the error messages, I had to exclude bits of code to find what exactly causes the problem):

Code:
define dspa reg
   sta {DATA}
   sti {ADDR},#{reg}
enddef

..

{dspa {DSP_ADSR1}}

by on (#81451)
A bug, was seeing the first '}' as the end of a define. That was prior to me adding arguments to defines. Unfortunately nobody tested it, sorry.

Please replace Bass::evalDefines with the below function in eval.cpp:

Code:
void Bass::evalDefines(string &line) {
  unsigned length = line.length();
  for(unsigned x = 0; x < length; x++) {
    if(line[x] == '{') {
      signed counter = 1;
      for(unsigned y = x + 1; y < length; y++) {
        if(line[y] == '{') counter++;
        if(line[y] == '}') counter--;
        if(line[y] == '}' && counter == 0) {
          string name = substr(line, x + 1, y - x - 1);
          if(!name.position("::")) name = { activeNamespace, "::", name };

          lstring header, args;
          header.split<1>(" ", name);
          if(header[1] != "") args.split(",", header[1]);

          foreach(define, defines) {
            if(header[0] == define.name && args.size() == define.args.size()) {
              string result;
              evalParams(result, define, args);
              line = string(substr(line, 0, x), result, substr(line, y + 1));
              defineExpandCounter++;
              return evalDefines(line);
            }
          }
          break;
        }
      }
    }
  }
}


Also, it's odd code that seems to assume A already has the value you want to write. I assume that was your intention.

Quote:
But one could decide that when the label separator and the instruction separator are set to the same, a label may appear only in the first position on a line.


That would break my macro expansions (it turns each line feed into a separator, so that error messages are on the same line# and I only have to replace data on the currently selected line block), and it would also require a specialized parsing grammar instead of just qsplit(";", line);

by on (#81452)
Thanks for the fix, it works.

Other problem, maybe a minor one, but can make some headache - no errors if an undefined label is used in a beq. I.e., no label anywhere: is defined, bra anywhere makes a error, while beq anywhere shows no error.

by on (#81455)
Here's a fixed version for the define and beq issues. Also added -overwrite option to forcefully overwrite existing files.

Code:
http://byuu.org/files/bass_v02.tar.bz2


Sorry again that you're effectively the beta tester here, but I appreciate the bug reports at any rate.