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!
- Never write to ROM addresses - Writing to ROM ($C000-$FFFF) will do nothing, but reading is safe
- Be careful with OS addresses - Writing to OS RAM can crash the system
- Validate addresses - Check that addresses are in valid RAM ranges
- Save your work - Low-level bugs can cause system crashes
- 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
- Use high-level statements when possible - They're safer and easier
- Document memory addresses - Use constants or comments
- Validate before writing - Check addresses are in safe ranges
- Use ADR() for addresses - More readable than raw numbers
- Test on emulator first - Safer than real hardware
Related Topics
- Hardware Registers - Complete hardware register reference
- Memory Map - Atari memory map
- Functions - Low-level functions
- Statements - Low-level statements