#c64 #retro #6502 #byte-stream #validation

c64-assembler

Commodore 64 assembler, outputs directly to .PRG or Dasm source code

1 unstable release

0.1.0 Jan 29, 2025

#463 in Programming languages

Download history 107/week @ 2025-01-26 32/week @ 2025-02-02

139 downloads per month

GPL-3.0-or-later

230KB
3K SLoC

C64 Assembler

The goal of this crate is to being able to compile C64 assembly directly from Rust.

The reasoning behind it is that in a typical C64 development workflow the programs are generated in a separate step and then being saved to a disk image. For generating disk images there are already crates out there like cmb.

However some projects require more control over the compilation stage and disk construction stage. Having a C64 assembler written in rust can build a bridge and allows custom disk formats and faster iterations during development.

Modules and functions

An crate::Application is organized in crate::Module and crate::Function. Modules can be shared between applications. A module public API is organized in functions. Multiple variations of functions can exists. By swapping out functions in a module a module can be size-optimized or CPU cycles optimized based on the actual needs of the program.

Usage

Building pattern

An application can be build using builder patterns.

use c64_assembler::builder::ApplicationBuilder;
use c64_assembler::builder::ModuleBuilder;
use c64_assembler::builder::InstructionBuilder;

let application = ApplicationBuilder::default()
    .name("Set black border")
    .include_vic20_defines()
    .module(
        ModuleBuilder::default()
            .name("main")
            .instructions(
                InstructionBuilder::default()
                    .add_basic_header()
                    .label("main_entry_point")
                    .lda_imm(0x00)
                    .comment("Load black color")
                    .sta_addr("VIC20_BORDER_COLOR")
                    .rts()
                    .build(),
            )
            .build(),
    )
    .build().unwrap();

Validating

Using the crate::validator::Validator to check for consistency.

use c64_assembler::validator::Validator;

let validation_result = application.validate();
assert!(validation_result.is_ok());

Generating dasm source

Using the crate::generator::DasmGenerator a dasm compatible assembly source can be generated.

use c64_assembler::generator::Generator;
use c64_assembler::generator::DasmGenerator;

let source = DasmGenerator::default().generate(application).unwrap();
println!("{}", source);

Would output

; --- Application: SET BLACK BORDER ---
; NOTE: This file is generated, do not modify

  processor 6502

VIC20_BORDER_COLOR = $D020

  org $0800

; --- Module begin: MAIN ---
  byte $00, $0C, $08     ; New basic line
  ; 10 SYS 2062
  byte $0A, $00, $9E, $20, $32, $30, $36, $32
  byte $00, $00, $00     ; End basic program

main_entry_point:
  lda #$00
  sta VIC20_BORDER_COLOR
  rts
; --- Module end: MAIN ---

Generating .PRG byte stream

Using the crate::generator::ProgramGenerator to generate the byte stream. The byte stream includes the loading address.

use c64_assembler::generator::{Generator, ProgramGenerator, print_hexdump};

let bytes = ProgramGenerator::default().generate(application).unwrap();
print_hexdump(&bytes);
0000:  00 08 00 0C  08 0A 00 9E  20 32 30 36  32 00 00 00
0010:  A9 00 8D 20  D0 60

Using macros (work in progress)

To reduce the boilerplating macros can be used. This is still under development. Expect less stability, error messages and some instructions not supported.

use c64_assembler_macro::application;

let application = application!(
    name="Set black border"
    include_vic20_defines
    module!(
        name="main"
        instructions!(
        include_basic_header
        main_entry_point:
            "Load black color into accumulator"
            lda #$00
            sta VIC20_BORDER_COLOR
            rts
        )
    )
).unwrap();

Dependencies