forge Documentation

low-level-programming

Low-Level Programming

Low-level programming in FORGE involves direct memory manipulation and hardware register access. Use with care!

Overview

FORGE provides several statements and functions for low-level programming:

  • Memory operations: POKE, PEEK, DPOKE, DPEEK, MOVE, MSET
  • Address operations: ADR function
  • Machine code execution: USR function
  • Hardware register access: Direct memory access

Memory Operations

POKE address, value / P.

Writes A Byte To Memory

Writes the value (modulo 256) to the memory location at address.

Example:

POKE 710, 0  ' Set color register 2
POKE 53279, 0  ' Read CONSOL register

PEEK(address) / P.(address)

Reads A Byte From Memory

Returns the value of memory location at address.

Example:

CONSOL = PEEK(53279)  ' Read console keys
IF (CONSOL AND 1) = 0 THEN ? "START pressed"

DPOKE address, value / D.

Writes A 16-Bit Number To Memory

Writes the value to the memory location at address and address+1, using standard CPU order (low byte first).

Example:

DPOKE 560, $4000  ' Set display list address

DPEEK(addr) / D.(addr)

Reads A 16-Bit Number From Memory

Returns the value of memory location addr and addr+1 as a 16 bit integer.

This is the same as doing PEEK(_addr_)+PEEK(_addr_+1)*256.

Example:

DLIST = DPEEK(560)  ' Get display list address

MOVE from, to, length / M.

Copies Bytes In Memory (Forward)

Copies length bytes in memory at address from to address to.

The MOVE version copies from the lower address to the upper address.

Example:

MOVE $4000, $5000, 256  ' Copy 256 bytes forward

-MOVE from, to, length / -.

Copies Bytes In Memory (Backward)

Copies length bytes in memory at address from to address to.

The -MOVE version copies from upper address to lower address. Use this when memory ranges overlap and from is lower than to.

Example:

-MOVE $5000, $4000, 256  ' Copy 256 bytes backward (overlapping)

Important: Choose the correct MOVE direction when ranges overlap:

  • If from < to, use -MOVE
  • If from > to, use MOVE

MSET address, length, value / MS.

Sets Memory To A Value

Writes length bytes in memory at given address with value.

This is useful to clear graphics or P/M data, or simply to set a string to a repeated value.

Example:

MSET $4000, 1024, 0  ' Clear 1KB of memory
MSET $D000, 4, 0  ' Clear Player 0-3 horizontal positions

Address Operations

ADR(var) / &var

Returns Memory Address

Returns the address of the variable, array, or string in memory.

Example:

DIM A(10)
ADDR = ADR(A)  ' or ADDR = &A

A$ = "Hello"
STR_ADDR = ADR(A$)  ' Address of string structure

Note: For strings, ADR() returns the address of the string structure (first byte is length), not the first character. This differs from Atari BASIC.

Machine Code Execution

USR(address[,num1 ...])

Calls Machine Code Subroutine

Low level function that calls the user supplied machine code subroutine at address.

Parameters are pushed to the CPU stack, with the LOW part pushed first, so the first PLA returns the HIGH part of the last parameter, and so on.

The value of A and X registers is used as a return value of the function, with A the low part and X the high part.

Example:

' PLA / EOR $FF / TAX / PLA / EOR $FF / RTS
DATA ml() byte = $68,$49,$FF,$AA,$68,$49,$FF,$60
FOR i=0 TO 1000 STEP 100
  ? i, USR(ADR(ml),i)
NEXT i

String Memory Operations

$(addr)

Returns String At Memory Address

Returns the string at memory address addr.

This is the inverse of ADR(), and can be used to create arbitrary strings in memory.

Example:

DATA x() byte = 2, $41, $42  ' Length 2, "AB"
? $( ADR(x) )  ' Prints "AB"

You can also store string addresses to reuse later:

x = ADR("Hello")
? $( x )  ' Prints "Hello"

%(n)

Floating Point At Memory Address

Returns the floating-point value stored at memory address n.

This function is special, as it is possible to use it also at the left side of an assignment, to store a floating point into an address:

%(1536) = 0.1234
? %(1536)  ' Prints 0.1234

Common Patterns

Reading Hardware Registers

' Read console keys
CONSOL = PEEK(53279)
IF (CONSOL AND 1) = 0 THEN ? "START"
IF (CONSOL AND 2) = 0 THEN ? "SELECT"
IF (CONSOL AND 4) = 0 THEN ? "OPTION"

' Read keyboard
KEY_CODE = PEEK(764)
IF KEY_CODE <> 255 THEN ? "Key: "; KEY_CODE

Writing Hardware Registers

' Set color registers
POKE 708, 0  ' Color 0
POKE 709, 16  ' Color 1
POKE 710, 32  ' Color 2
POKE 711, 48  ' Color 3

' Set Player/Missile positions
POKE $D000, 80  ' Player 0 X position
POKE $D001, 100  ' Player 1 X position

Memory Clearing

' Clear screen memory
SCREEN = DPEEK(88)  ' Screen memory address
MSET SCREEN, 960, 0  ' Clear GRAPHICS 0 screen

' Clear Player/Missile data
PM_ADDR = PMADR(0)
MSET PM_ADDR, 256, 0  ' Clear Player 0

Copying Data

' Copy display list
OLD_DL = DPEEK(560)
NEW_DL = $4000
MOVE OLD_DL, NEW_DL, 100  ' Copy display list
DPOKE 560, NEW_DL  ' Point to new display list

Safety Warnings

⚠️ Low-level operations can crash your system!

  1. Never write to ROM addresses - Writing to ROM ($C000-$FFFF) will do nothing, but reading is safe
  2. Be careful with OS addresses - Writing to OS RAM can crash the system
  3. Validate addresses - Check that addresses are in valid RAM ranges
  4. Save your work - Low-level bugs can cause system crashes
  5. Test incrementally - Test each low-level operation separately

Safe Memory Ranges

Generally Safe for Writing

  • $0400-$7FFF - User RAM (varies by system)
  • $4000-$7FFF - Screen memory (graphics modes)
  • $D000-$D7FF - Hardware registers (read/write safe)
  • $D400-$D7FF - POKEY registers
  • $D000-$D01F - GTIA registers

Read-Only (Hardware)

  • $D200-$D2FF - ANTIC registers (mostly read-only)
  • $D300-$D3FF - PIA registers

Dangerous (OS/System)

  • $0000-$03FF - Zero page and stack (OS uses)
  • $E000-$FFFF - OS ROM (read-only)
  • $D800-$DFFF - Cartridge space (may be ROM)

Best Practices

  1. Use high-level statements when possible - They're safer and easier
  2. Document memory addresses - Use constants or comments
  3. Validate before writing - Check addresses are in safe ranges
  4. Use ADR() for addresses - More readable than raw numbers
  5. Test on emulator first - Safer than real hardware

Related Topics