C64jasm - a new take on C64 macro assembling

Introduction

For the past couple of months I’ve worked on the C64 VISIO 2018 invitation intro (YouTube capture at the bottom of this post). I upped the difficulty level a bit by first designing and implementing a new 6502 macro assembler called c64jasm, and then using that to develop my demo. While I haven’t had the time to polish the assembler to, say, KickAssembler level, my tool turned out to be a real joy to work with. This post highlights some of its features that I believe make it somewhat unique in its small niche.

These were my initial design choices for c64jasm:

  • Written in TypeScript & running on Node. Easy multi-platform environment with easy to install dependencies.
  • Ability to extend the assembler’s functionality by calling into user-defined JavaScript modules directly from the .asm source.
  • A sufficiently powerful pseudo language (macros, conditionals, for-loops, etc) but build on the JavaScript extension mechanism for more complex pseudo ops like LUT creation and file format parsing.
  • Node-style “watch” support: automatically recompile if any source files change. Design it for efficiency.
  • VSCode integration (for on-the-fly compilation and error reporting within the IDE, launch output binary in VICE.)

The choice of TypeScript was very deliberate. I’ve been meaning to learn the language for quite some time, and developing a macro assembler in it was a good way to learn & evaluate TypeScript. TypeScript turned out to be a great language: supports functional programming style well (and so well-suited for developing compilers) and has a simple but powerful static typing system.

The assembler extension mechanism is the main feature that differentiates c64jasm from traditional 6502 macro assemblers and I’ll spend the rest of this blog talking about this mechanism.

This blog post assumes that you’re at least somewhat familiar with macro assemblers and that you know a bit of JavaScript.

Why do I need to extend my assembler?

So why does one need to extend the assembler in the first place? I have my assembly, macros and other pseudo directives, isn’t this enough? Well, you need all of these too. But some things like loading graphics files, generating lookup tables, etc. can be better expressed in a real programming language. Quite commonly a demo project is built using a build script that processes LUTs, graphics, and other assets into hex listings to be included in the main assembly source.

Extending functionality

Traditionally, extending the functionality of an application or a tool means that you’d first need to learn the app’s extension API, figure out how to build your extension (say .DLLs or a Java .jar file), install it (and maybe debug your PATH or CLASSPATH when it fails to load), and once all done, the feature becomes available in your tool and you use it to develop your content.

The approach I took in c64jasm is that assembler extensions are just a bunch of source files in your project’s source directory. Extensions shouldn’t require a separate build or installation step and the overhead of creating a new one for any specific purpose should be minimal. Ideally the extensions should be runnable stand-alone without the assembler and that you should be able to debug and test them using standard debugging tools.

Before we get deeper into c64jasm assembler plugins, let’s review some of the c64jasm pseudo directives.

C64jasm pseudo ops

If you’ve used something like KickAssembler, ACME or 64tass, these should look pretty familiar.

Declaring pseudo variables: You can declare pseudo variables using !let. This is basically the same as KickAssembler’s .const/.var (many other assemblers commonly have this as EQU.)

Macros:

Symbols declared within a macro are local to that macro. You can also declare macros within macros.

If/elif/else:

For-loops:

As opposed to a C-style “declare loop variable, check condition, increment” for loops, c64jasm’s for is similar to Python’s for-statement. You use a function called range() that returns a list of integers and the for-loop just iterates over the elements of this list, assigning the current list element to the bound loop variable i. E.g., these two are conceptually the same:

The “emit byte” pseudo !byte supports emitting a list of bytes when passed an integer list. The following two lines are equivalent:

C64jasm extensions

So with the basic c64jasm pseudo ops explained, let’s continue to c64jasm extensions. A c64jasm extension is just a JavaScript .js module. To use such a .js module in your assembly source, you bind it to a name with !use "path/to/plugin.js". This name becomes a function that you can call in your assembly source file.

Let’s illustrate this with some code to generate a sine lookup table. First the plugin code:

Here’s how you’d use the above in your .asm source file:

As !byte and !word accept a list as an argument, the above can be written simply as:

The nice thing about this approach is that I can keep the assembler implementation simple. The assembler doesn’t need a large standard library of math and other utility functions as this functionality can be easily expressed as tiny JavaScript modules.

Sure, generating a sine table would be pretty easy without an extension too (and this is totally supported by c64jasm):

Where I find the JavaScript extension mechanism really shines is importing assets (sprites from .spd, .sid, etc.). Here’s how I compile SpritePad .spd files into my demo binary:

Using the above in an .asm file:

!use "./spd" as spd

; Load 'hirmu.spd' and store the returned JavaScript object in a
; variable called 'hirmu_sprite'
!let hirmu_sprite = spd("../sprites/hirmu.spd")

At this point, no code or bytes were yet emittted. The .spd file contents are now in a pseudo variable called hirmu_sprite. This is just a JavaScript object with fields like numSprites, enableMask, data that can be accessed from .asm with the dot operator (e.g., sprite_data.numSprites.)

With this, I can expand the sprite data into the output binary. Or I can use to emit code to setup sprite registers (enable bits, individual and multi color values, etc).

Emitting actual sprite data for all the sprites contained within the .spd file:

I also define a macro that expands machine code to write sprite registers based on the .spd file contents:

I use the same pattern for all the different asset types used in my demo. Here’s how I pull the SID tune into the binary:

Using the loaded SID tune from the .asm source:

All of these content plugins are just normal .js files that can be run from the command line with node. This enables you to use standard tools like VSCode to debug them as stand-alone JavaScript modules. I prefer this code to be part of the project source tree rather than buried somewhere within c64jasm’s standard libraries.

Epilogue

OK, that’s about it for now on c64jasm plugins. I will probably write another blog later about using c64jasm in VSCode (with quick re-compile-on-save and launch .prg in VICE.) I tweeted about it a while ago, you can check out the video if you’re curious about this.

Oh by the way, c64jasm is available on GitHub and is already quite usable. However, it’s still very much work-in-progress. If you’re into this type of stuff and feeling adventurous, feel free to give it a try. Otherwise, stick with the more established C64 assemblers. :)

VISIO 2018 invitation demo capture