# Programming in c65gm c65gm is a high-level language for 6502 assembly programming that combines modern programming constructs with direct hardware access. Originally designed for Commodore 64 development, it compiles to efficient 6502 assembly code. ## Table of Contents 1. [Getting Started](#getting-started) 2. [Variables and Types](#variables-and-types) 3. [Expressions and Operators](#expressions-and-operators) 4. [Control Flow](#control-flow) 5. [Functions](#functions) 6. [Memory Operations](#memory-operations) 7. [Code Blocks](#code-blocks) 8. [Preprocessor](#preprocessor) 9. [Writing Libraries](#writing-libraries) 10. [Common Patterns](#common-patterns) --- ## Getting Started ### Your First C64 Program Here's a simple complete program that changes the screen border color: ```c65 #INCLUDE #INCLUDE GOTO start FUNC main BYTE borderColor @ $D020 borderColor = color_green FEND LABEL start main() ``` ### Creating a C64 Executable To create a runnable C64 program, always start with: ```c65 #INCLUDE ``` This creates a valid C64 executable with a BASIC loader (`0 SYS 2064`) and sets up the proper memory layout. ### Program Structure A typical c65gm program for C64 follows this pattern: ```c65 #INCLUDE #INCLUDE // Optional: standard C64 definitions GOTO start // Jump over your function definitions // Variables BYTE counter = 0 WORD screen = $0400 // Functions - must be declared before we use them. FUNC processData // Do something FEND FUNC initialize counter = 10 processData() FEND // Entry point - execution starts here LABEL start initialize() ``` **Important:** The `GOTO start` jumps over your function definitions. Without it, the CPU would try to execute directly into your function code, causing a crash. Always put `GOTO start` before your functions, and `LABEL start` before your actual program entry point. --- ## Variables and Types ### BYTE Variables BYTE variables store 8-bit values (0-255). ```c65 BYTE count // Uninitialized BYTE speed = 5 // Initialized to 5 BYTE screen @ $D020 // Memory-mapped to specific address BYTE CONST MAX_SPEED = 10 // Constant (recommended over #DEFINE) ``` ### WORD Variables WORD variables store 16-bit values (0-65535). ```c65 WORD counter // Uninitialized WORD address = $C000 // Initialized to hex value WORD message = "Hello" // String literal (pointer to text) WORD screenPtr @ $FB // Zero-page pointer WORD CONST SCREEN_RAM = $0400 // Constant ``` ### Memory-Mapped Variables Variables can be placed at specific addresses using `@`: ```c65 BYTE borderColor @ $D020 // VIC-II border color WORD irqVector @ $0314 // IRQ vector WORD zpPointer @ $FB // Zero-page variable (fast access) ``` ### Constants Use `CONST` keyword for named constants (preferred over preprocessor defines): ```c65 BYTE CONST MAX_ITEMS = 100 WORD CONST SCREEN_START = $0400 WORD CONST CHAR_ROM = $D000 // Usage BYTE itemCount = MAX_ITEMS screen = SCREEN_START ``` --- ## Expressions and Operators ### Number Formats ```c65 value = 123 // Decimal value = $FF // Hexadecimal ($ prefix) value = %11111111 // Binary (% prefix) ``` ### Arithmetic Operators ```c65 result = 10 + 5 // Addition result = 100 - 5 // Subtraction ``` ### Bitwise Operators ```c65 mask = value & $FF // Bitwise AND flags = flags | $01 // Bitwise OR toggle = value ^ $FF // Bitwise XOR ``` ### Increment and Decrement ```c65 counter++ // Increment by 1 counter-- // Decrement by 1 index++ lives-- ``` ### Critical: No Operator Precedence **Expressions evaluate strictly left to right!** There is no operator precedence. ```c65 result = 2+3*4 // Evaluates as (2+3)*4 = 20, NOT 2+(3*4) = 14 value = 100-20+5 // Evaluates as (100-20)+5 = 85 ``` For complex expressions, use temporary variables: ```c65 // Instead of: result = (b - c) + a temp = b - c result = a + temp ``` --- ## Control Flow ### IF Statements Basic conditional execution: ```c65 IF count == 10 result = 1 ENDIF IF value > threshold process() ELSE skip() ENDIF ``` Supported comparison operators: `=` `==` `<>` `!=` `>` `<` `>=` `<=` Single-parameter IF treats 0 as false, non-zero as true: ```c65 IF running // Executes if running != 0 update() ENDIF ``` ### WHILE Loops Loop while condition is true: ```c65 WHILE counter < 100 counter++ process(counter) WEND WHILE running gameLoop() WEND WHILE x != y x++ WEND ``` ### FOR Loops Loop with automatic counter: ```c65 FOR i = 0 TO 10 screen = i NEXT FOR x = 0 TO 255 STEP 2 result = result + x NEXT FOR counter = start TO finish process(counter) NEXT ``` ### BREAK Exit a loop early: ```c65 FOR i = 0 TO 100 IF i == 50 BREAK ENDIF process(i) NEXT WHILE running IF error BREAK ENDIF update() WEND ``` --- ## Functions ### Declaring Functions ```c65 FUNC initialize BYTE temp = 0 screen = temp FEND FUNC setColor(color, brightness) borderColor = color backgroundColor = brightness FEND ``` ### Calling Functions ```c65 initialize() setColor(1, 14) process(xpos, ypos, spriteData) ``` ### Parameter Passing Modes Parameters can have different access modes: - `in:` - Read-only (default if not specified) - `out:` - Write-only (for returning values) - `io:` - Read-write ```c65 FUNC add(in:a, in:b, out:result) result = a + b FEND FUNC swap(io:x, io:y) BYTE temp = x x = y y = temp FEND ``` ### Local Variables Variables declared inside functions are local: ```c65 FUNC calculate(value) BYTE local = 10 // Only exists inside this function WORD temp // Local temporary temp = value + local FEND ``` ### Curly Brace Syntax Alternative parameter syntax using curly braces: ```c65 FUNC process({BYTE value} out:{BYTE result}) result = value + 1 FEND ``` ### Returning from Functions ```c65 FUNC checkValue(value) IF value == 0 EXIT // Return early ENDIF process(value) FEND ``` --- ## Memory Operations ### PEEK - Reading Memory Read a byte from memory: ```c65 value = PEEK $D020 // Read from absolute address char = PEEK screenPtr[index] // Read with offset byte = PEEK pointer // Read from pointer ``` **Important:** For indexed access, the address must be a WORD variable in zero page. ```c65 WORD buffer @ $FB // Zero-page pointer value = PEEK buffer[10] // Read buffer+10 ``` ### POKE - Writing Memory Write a byte to memory: ```c65 POKE $D020 WITH 0 // Write to absolute address POKE screenPtr[index] WITH char // Write with offset POKE pointer WITH value // Write to pointer ``` ### PEEKW - Reading 16-bit Words Read a 16-bit value from memory: ```c65 WORD address address = PEEKW $FFFC // Read reset vector WORD buffer @ $FB value = PEEKW buffer[10] // Read word at buffer+10 ``` ### POKEW - Writing 16-bit Words Write a 16-bit value to memory: ```c65 POKEW $0314 WITH irqHandler // Set IRQ vector POKEW dataPtr[0] WITH address // Write word with offset ``` ### POINTER - Setting Pointers Set a pointer to an address: ```c65 POINTER screenPtr TO $0400 POINTER bufferPtr TO dataBuffer POINTER funcPtr TO myFunction ``` --- ## Code Blocks ### ASM Blocks Inline assembly for direct hardware control: ```c65 ASM lda #$00 sta $d020 jsr $ffd2 ENDASM ``` #### Referencing Variables in ASM Global variables can be referenced directly: ```c65 BYTE temp = 5 ASM lda temp clc adc #10 sta temp ENDASM ``` Local variables (inside FUNC) need pipe delimiters `|varname|`: ```c65 FUNC calculate(value) BYTE local = 10 ASM lda |local| // Reference local variable clc adc value // Global parameter sta |local| ENDASM FEND ``` ### SCRIPT Blocks Generate assembly code at compile time using Starlark (Python-like): ```c65 // Generate a table of squares SCRIPT print("squares:") for i in range(256): print(" !8 %d" % (i * i % 256)) ENDSCRIPT ``` #### Sine Table Example ```c65 SCRIPT import math print("sintable:") for i in range(256): angle = (i * 2.0 * math.pi) / 256.0 sine = math.sin(angle) value = int((sine + 1.0) * 127.5) print(" !8 %d" % value) ENDSCRIPT ``` #### Referencing Variables Use `|varname|` syntax in generated assembly: ```c65 BYTE tableSize = 64 SCRIPT print("lookup:") for i in range(64): print(" !8 %d" % (i * 2)) print(" // Size stored in |tableSize|") ENDSCRIPT ``` --- ## Preprocessor ### Comments ```c65 // Single-line comment BYTE counter = 0 // End-of-line comment ``` In ASM blocks, use assembly syntax: ```c65 ASM lda #$00 ; Assembly comment ENDASM ``` In SCRIPT blocks, use Python syntax: ```c65 SCRIPT # Python-style comment print("hello") ENDSCRIPT ``` ### Include Files ```c65 #INCLUDE mylib.c65 // Relative to current file #INCLUDE lib/string.c65 // Subdirectory #INCLUDE // Search in C65LIBPATH ``` Include guards prevent multiple inclusion: ```c65 #IFNDEF __MY_LIBRARY #DEFINE __MY_LIBRARY = 1 // Library code here #IFEND ``` ### Define Macros Text substitution macros: ```c65 #DEFINE MAX_SPEED = 10 #DEFINE SCREEN = $$0400 // $$ escapes to literal $ speed = MAX_SPEED // Replaced with 10 ``` Special characters in defines: ```c65 #DEFINE SPACE = $20 // Space character (hex 20) #DEFINE NEWLINE = $0D // Carriage return #DEFINE HEXADDR = $$D020 // Literal "$D020" text ``` **Note:** Prefer `BYTE CONST` and `WORD CONST` over `#DEFINE` for constants. ### Conditional Compilation ```c65 #IFDEF DEBUG BYTE debugFlag = 1 #IFEND #IFNDEF __LIB_INCLUDED #DEFINE __LIB_INCLUDED = 1 // Include library code #IFEND ``` ### Pragma Directives Control compiler behavior: ```c65 #PRAGMA _P_USE_LONG_JUMP 1 // Use JMP instead of branches #PRAGMA _P_USE_IMMUTABLE_CODE 1 // No self-modifying code (for ROM) #PRAGMA _P_USE_CBM_STRINGS 1 // Use PETSCII encoding ``` ### Debug Directives ```c65 #PRINT Compiling main module // Print during compilation #HALT // Stop compilation ``` --- ## Writing Libraries When creating a library file to be included by other programs, use this structure: ```c65 #IFNDEF __MY_LIBRARY #DEFINE __MY_LIBRARY = 1 GOTO lib_mylib_skip // Jump over library code // Library variables WORD lib_mylib_buffer // Library functions FUNC lib_mylib_initialize lib_mylib_buffer = $C000 FEND FUNC lib_mylib_process(value) // Do something FEND // Skip label - execution continues here after GOTO LABEL lib_mylib_skip #IFEND ``` **Key points for libraries:** 1. **Include guard** - Use `#IFNDEF` to prevent multiple inclusion 2. **GOTO skip** - Jump over all library code immediately 3. **LABEL skip** - Place at the end so GOTO jumps past everything 4. **Naming convention** - Prefix all names with `lib_yourlib_` to avoid conflicts This ensures when someone does `#INCLUDE `, the library functions are defined but not executed. --- ## Common Patterns ### Complete Working Example Here's a complete C64 program showing all the pieces together: ```c65 #INCLUDE #INCLUDE GOTO start // Variables BYTE frameCount = 0 WORD screenPtr @ $FB // Functions FUNC initialize BYTE borderColor @ $D020 borderColor = color_black POINTER screenPtr TO $0400 FEND FUNC updateScreen BYTE color color = frameCount & $0F POKE screenPtr[0] WITH color frameCount++ FEND // Entry point LABEL start initialize() WHILE 1 updateScreen() WEND ``` ### Screen Manipulation ```c65 WORD CONST SCREEN = $0400 WORD CONST COLOR_RAM = $D800 BYTE CONST SCREEN_WIDTH = 40 BYTE CONST SCREEN_HEIGHT = 25 FUNC clearScreen WORD screenPtr @ $FB POINTER screenPtr TO SCREEN WORD remaining = 1000 WHILE remaining > 0 POKE screenPtr[0] WITH 32 // Space character screenPtr++ remaining-- WEND FEND ``` ### String Handling ```c65 // Print null-terminated string FUNC printString(textPtr) BYTE char char = PEEK textPtr[0] WHILE char != 0 ASM lda |char| jsr $FFD2 // CHROUT ENDASM textPtr++ char = PEEK textPtr[0] WEND FEND ``` ### Sprite Manipulation ```c65 WORD CONST VIC2 = $D000 BYTE spriteX @ VIC2+0 BYTE spriteY @ VIC2+1 BYTE spriteEnable @ VIC2+21 FUNC enableSprite(spriteNum) BYTE mask mask = 1 FOR i = 0 TO spriteNum mask = mask * 2 NEXT spriteEnable = spriteEnable | mask FEND ``` ### Zero Page Optimization For frequently accessed pointers, use zero page: ```c65 WORD fastPtr @ $FB // Zero page = fast indexed access FUNC processBuffer(buffer, size) POINTER fastPtr TO buffer WHILE size > 0 BYTE value value = PEEK fastPtr[0] // Process value fastPtr++ size-- WEND FEND ``` ### Interrupt Handlers ```c65 WORD CONST IRQ_VECTOR = $0314 WORD oldIRQ FUNC installIRQ ASM sei // Disable interrupts ENDASM oldIRQ = PEEKW IRQ_VECTOR POKEW IRQ_VECTOR WITH myIRQ ASM cli // Enable interrupts ENDASM FEND LABEL myIRQ // IRQ handler code ASM // Save registers pha txa pha tya pha // Do IRQ work inc $d020 // Restore and return pla tay pla tax pla rti ENDASM ``` ### Lookup Tables Generate tables at compile time: ```c65 ASM colorTable: ENDASM SCRIPT colors = [0, 1, 15, 12, 11, 9, 2, 8] for c in colors: print(" !8 %d" % c) ENDSCRIPT ``` ### Delay Loops ```c65 FUNC delay(frames) BYTE raster @ $D012 BYTE oldRaster WHILE frames > 0 oldRaster = raster WHILE raster == oldRaster // Wait for raster to change WEND frames-- WEND FEND ``` ### Bit Manipulation ```c65 // Set bit flags = flags | %00000001 // Set bit 0 // Clear bit flags = flags & %11111110 // Clear bit 0 // Toggle bit flags = flags ^ %00000001 // Toggle bit 0 // Test bit IF flags & %00000001 // Bit 0 is set ENDIF ``` --- ## Best Practices ### 1. Use Constants for Magic Numbers ```c65 // Bad POKE $D020 WITH 5 // Good BYTE CONST COLOR_GREEN = 5 BYTE borderColor @ $D020 borderColor = COLOR_GREEN ``` ### 2. Zero Page for Performance Place frequently accessed pointers in zero page ($00-$FF): ```c65 WORD screenPtr @ $FB // Fast indexed access WORD tempPtr @ $FD ``` ### 3. Watch Expression Evaluation Order Remember: left-to-right evaluation, no precedence! ```c65 // Be careful with expressions result = 2 + 3 * 4 // = 20, not 14 // Use temps for clarity temp = 3 * 4 result = 2 + temp // Now = 14 ``` ### 4. Include Guards Always use include guards in library files: ```c65 #IFNDEF __MY_LIB #DEFINE __MY_LIB = 1 // Library code #IFEND ``` ### 5. Comment Your Code ```c65 // Explain what and why, not how FUNC calculateTrajectory(velocity, angle) // Use fixed-point math (8.8 format) // because we don't have floating point WORD xVel WORD yVel // ... FEND ``` ### 6. Use Meaningful Names ```c65 // Bad BYTE x BYTE y // Good BYTE playerXPos BYTE playerYPos ``` --- ## Further Resources - See `commands.md` for complete command reference - See `syntax.md` for detailed syntax rules - Check the `lib/` directory for example library code - Set `C65LIBPATH` environment variable to specify library search paths --- ## Quick Reference Card ```c65 // Variables BYTE varName = 10 WORD address = $C000 BYTE CONST MAX = 100 // Control Flow IF x == 10 // code ENDIF WHILE x < 100 x++ WEND FOR i = 0 TO 10 // code NEXT // Functions FUNC myFunc(in:param1, out:result) result = param1 + 1 FEND // Memory value = PEEK $D020 POKE $D020 WITH 5 address = PEEKW $FFFC POKEW $0314 WITH handler // Operators + - * / // Arithmetic & | ^ // Bitwise ++ -- // Increment/Decrement == != < > <= >= // Comparison // Blocks ASM // assembly code ENDASM SCRIPT # Python code ENDSCRIPT ```