C64 KickAss Workshop Part 1

Hello, World!

Assembly language is the closest you can get to the machine — every instruction directly controls the 6502 CPU. It’s more work than BASIC, but the payoff is total control and blazing speed. The classic first step: get something on screen.

We’ll use Kick Assembler (KickAss), a cross-assembler that runs on your modern machine and outputs C64-compatible .prg files. The Kick Assembler Reference Manual covers everything the assembler can do.

Objective

Write a KickAssembler program that clears the screen, sets custom colours, and displays a message of your choosing.

Requirements

Bonus


Getting Started

What You Need

  1. Java (version 5.0 or later) — Kick Assembler runs on Java
  2. Kick Assembler — download from theweb.dk/KickAssembler
  3. A text editor — any editor works (VS Code, Sublime Text, Notepad++, etc.)
  4. VICE emulator — see Part 0 of the BASIC workshop for setup instructions

Assemble and Run

Save your code as a .asm file (e.g., hello.asm), then assemble:

java -jar KickAss.jar hello.asm

This produces hello.prg. Open it in VICE by dragging it onto the emulator window, or use File → Autostart disk/tape image.

Alternatively, for Linux, here’s a script to assemble and immediately test using VICE:

#!/bin/sh
java -jar /path/to/KickAss.jar "$1" -showmem && x64sc -autostart "${1%.asm}.prg"

Tip: Save the script as “kickassrun.sh” (and ensure you chmod +x kickassrun.sh so you can actually run it!), and remember to set the KickAss.jar and x64sc paths as needed. Then you can run it with ./kickassrun.sh hello.asm.


Resources / Reference

Program Structure

A C64 KickAss program has two sections: a BASIC loader and your machine code. The BASIC loader is a tiny program that tells the C64 to jump to your code when you type RUN.

Use this template for all programs in this workshop:

.pc = $0801 "Basic Upstart"    // BASIC loader goes here
:BasicUpstart(start)

.pc = $0810 "Program"
start:
    // Your code goes here

Line by line:

  • .pc = $0801 — Sets where the next bytes go in memory. $0801 is where the C64 stores BASIC programs.
  • :BasicUpstart(start) — A Kick Assembler macro that generates a one-line BASIC program (10 SYS 2064). When the C64 runs this, it jumps straight to your start label.
  • .pc = $0810 — Sets the location for your actual machine code. $0810 is right after the BASIC loader in memory.
  • start: — A label marking where your program begins. Labels end with : when declared.

The two .pc lines are needed because the BASIC loader and your code live at different addresses — think of them as two sections in the same file. Every program you write will start with this exact template.

CPU Registers

The 6502 CPU has three main registers — think of them as built-in variables:

  • A (Accumulator): The main register for loading, storing, and arithmetic
  • X (X Index): Used as a counter or offset for indexed addressing
  • Y (Y Index): Another counter/index register, similar to X

Each holds one byte (0–255).

Number Formats

Kick Assembler supports three number formats:

Prefix Format Example
(none) Decimal lda #42
$ Hexadecimal lda #$2a
% Binary lda #%00101010

Hex is used heavily in C64 programming because hardware addresses are conventionally written in hex (e.g., screen memory at $0400, border colour at $d020).

LDA / STA

Load a value into the Accumulator and store it to memory.

lda #BLACK           // Load the VALUE "BLACK" (0) into A
sta $d020            // Store A to the ADDRESS $d020

The # is critical: # means “this exact value” (immediate mode). Without #, the CPU reads FROM that memory address instead.

lda #5               // A = 5 (the number five)
lda $0400            // A = whatever byte is stored at address $0400

LDX / INX / DEX

Load, increment, and decrement the X register — essential for loops and counting.

ldx #0               // X = 0
inx                  // X = 1
inx                  // X = 2
dex                  // X = 1

ldy, iny, and dey are the equivalent instructions for the Y register. dex and dey are commonly used to count down in loops — when the counter reaches zero, the zero flag is set, so you can use bne to keep looping.

Labels, JMP, and BEQ

Labels mark positions in code. jmp jumps unconditionally. beq jumps only if the last result was zero.

loop:
    inc $d020            // Increment border colour
    jmp loop             // Jump back — infinite loop!

beq (Branch if EQual to zero) checks the CPU’s zero flag, which is set automatically when any operation produces a zero result:

lda message,x        // Load a byte
beq done             // If it was zero, jump to 'done'

bne (Branch if Not Equal to zero) is the opposite — jumps when the result is NOT zero.

Halting: Always end your program with a jump to itself so the CPU doesn’t run into garbage memory:

done:
    jmp done             // Loop forever (halt)

The shorthand jmp * does the same thing — * means “the current address.”

Multi labels let you reuse the same label name. They start with ! and use + or - to refer to the next or previous instance:

    ldx #100
!loop: inc $d020
    dex
    bne !loop-           // Jump to the PREVIOUS !loop

JSR

Jump to SubRoutine — call a routine and return when done.

The C64’s KERNAL ROM provides useful built-in routines:

jsr $e544            // Call KERNAL: clear the screen

When the routine finishes (with rtsreturn from subroutine), execution continues after the jsr.

.text and .byte

Embed data directly in your program.

.text stores a string as C64 screen code bytes — ready to write directly to screen memory.

.byte stores raw byte values.

message:
    .text "hello"        // 5 bytes of screen codes
    .byte 0              // A zero byte (end-of-string marker)

data:
    .byte 1,2,3,4,5      // 5 raw bytes

A zero byte at the end is a common convention for marking where a string ends — your loop can check for it with beq.

Use lowercase in .text strings. KickAssembler maps lowercase source letters to the screen codes for A–Z. Uppercase source letters map to shifted/graphics characters instead.

Indexed Addressing

Use X (or Y) as an offset when loading or storing. This lets you process strings and arrays:

ldx #2
lda message,x        // Load byte at (message address + 2)
sta $0400,x          // Store to screen memory position 2

If message contains "hello", then with X=2, lda message,x loads 'l' (third character — counting starts at 0).

Printing Text to Screen

Combine indexed addressing with a loop to copy a string to screen memory one character at a time:

    ldx #0               // Start at the first character
!loop:
    lda message,x        // Load next character from our string
    beq done             // If it's zero, we've reached the end
    sta $0400,x          // Write it to screen memory
    inx                  // Move to the next character
    jmp !loop-           // Repeat
done:
    jmp done             // Halt

message:
    .text "hello world"
    .byte 0              // End-of-string marker

This is the core pattern: load a character, check if it’s the zero terminator, store it to screen memory, advance to the next position, and repeat. Everything else — clearing the screen, setting colours, choosing where on screen your text appears — builds on top of this loop.

Screen Memory

The C64 screen is 40 columns × 25 rows. Two memory regions control what you see:

  • Screen memory ($0400): Which character is displayed at each position
  • Colour memory ($d800): The foreground colour of each character (0–15)

To calculate the address for a specific row and column:

$0400 + (row × 40) + column (screen) or $d800 + (row × 40) + column (colour)

Kick Assembler can do this maths at assemble time with .var:

.var screenPos = $0400 + 12 * 40 + 13   // Row 12, column 13
.var colourPos = $d800 + 12 * 40 + 13

Comments

Kick Assembler uses C-style comments:

// This is a line comment
lda #0               // Comment after code

/* This is a
   block comment */

Colour Constants

Kick Assembler has built-in C64 colour constants you can use by name:

Constant Value Constant Value
BLACK 0 ORANGE 8
WHITE 1 BROWN 9
RED 2 LIGHT_RED 10
CYAN 3 DARK_GRAY 11
PURPLE 4 GRAY 12
GREEN 5 LIGHT_GREEN 13
BLUE 6 LIGHT_BLUE 14
YELLOW 7 LIGHT_GRAY 15

These values work for border ($d020), background ($d021), and colour memory ($d800).

Example:

lda #BLACK
sta $d020            // Set border to black
lda #BLUE
sta $d021            // Set background to blue

Quick Reference

Address Purpose
$0400 Screen memory start (40 columns × 25 rows)
$d800 Colour memory start (foreground colour per character, 0–15)
$d020 Border colour register (0–15)
$d021 Background colour register (0–15)
$e544 KERNAL routine: clear screen
Instruction What It Does
lda #value Load a value into A
sta address Store A to a memory address
ldx #value Load a value into X
inx / dex Increment / decrement X by 1
iny / dey Increment / decrement Y by 1
jmp label Jump to a label (unconditional)
beq label Branch to label if last result was zero
bne label Branch to label if last result was NOT zero
jsr address Call a subroutine

Tips for Success


Back to Home