Skip to content

6502 without the memory maze

The processor may see 64 KiB at a time. Your D/BASIC program is not a 64 KiB program.

The 6502 is an important D/BASIC destination, not a novelty mode. It brings a wonderfully small, understandable processor into a language with objects, widgets, large arrays, packaged resources, and modern operating-system services. The trick is not pretending the processor has a different address bus. The trick is making that address bus the runner's responsibility rather than yours.

The first profile targets 512 KiB

Many older BASIC environments were squeezed into 16–64 KiB alongside the interpreter, screen, operating system, source text, variables, and string heap. Programs learned to survive through overlays, tiny buffers, bank registers, and machine-specific copy routines.

D/BASIC removes that ceiling from the language model. Its first 6502 target profile sets a nominal useful workspace target of 512 KiB. Later target profiles can offer more without adding a new dialect or requiring a source rewrite. The 512 KiB figure is the first target profile, not a permanent language limit, a flat pointer, or an unconditional allocation receipt.

Before execution, the runner admits the image, static state, and the program's known minimum workload. Dynamic demand can still arise later from REDIM, growing strings, new objects, resources, or provider work. Each such allocation is admitted atomically when requested: if capacity is unavailable, the runner refuses the request and leaves the old binding and published state intact. A smaller machine never continues with a half-applied allocation on the strength of a marketing number.

This distinction matters:

Fact Who deals with it?
the 6502 has a 16-bit address window runner and hardware adapter
a particular byte currently lives in a bank or page memory provider
an array has 100,001 elements D/BASIC program
a target cannot satisfy an admitted allocation governed capacity refusal

You still design within a real machine's published capacity. You simply do it with values and bounds instead of addresses and bank numbers.

Write DIM, not a bank-switching ritual

DIM Samples%(0 TO 4095)

ARRAYFILL Samples%(), -1
Samples%(2048) = 42
PRINT Samples%(2048)
ERASE Samples%
END

That program contains no far pointer, page call, bank selector, or copy-through buffer. DIM asks for an array with a shape. The runtime chooses storage, pages values as needed, validates every access, and keeps the binding authoritative until ERASE releases it.

Arrays, variable strings, objects, and other dynamic values live behind governed handles. A handle is never a machine address. It can name storage in a resident region, a banked pool, or another admitted tier while the same DBC image keeps the same meaning.

Banking is deliberately invisible

The toolchain and runner own code and constant placement. The 6502 runner keeps a bounded working set in reach and pages immutable DBC regions through its private adapter. On a miss, the runner performs the bounded bank transaction and then continues the same bytecode operation.

None of these implementation details enter application source:

  • no bank register names;
  • no overlay annotations;
  • no near/far pointer types;
  • no “copy this bank into conventional memory first” API; and
  • no target test around an array, object, or procedure call.

If a bank is selected on entry to a runner operation, the runner restores its required mapping before returning. Event delivery, object identity, array bounds, and refusal behavior therefore do not depend on which page happened to be resident.

One optional memory policy, still no banking

By default, a runner may satisfy governed storage from every tier it honestly offers: fast resident memory, the ordinary program pool, and a slower media tier. Most programs should keep that default.

OPTION TIER RAM

OPTION TIER RAM is the one source-visible memory ceiling. It excludes the media tier when an application requires RAM-only latency; it does not select a bank, choose an address, or promise that every allocation will fit. Exhausting the allowed tiers produces a capacity refusal instead of a silent spill or corruption.

Intents are the acceleration API

Memory transparency solves capacity. Intents solve the other classic 6502 problem: asking a small CPU to perform every pixel, floating-point operation, digest, transfer, and database step one byte at a time.

INTENT GFX.BEGIN_FRAME()
INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
INTENT GFX.DRAW_TEXT(12, 12, "HELLO FROM D/BASIC", 3, -1, 2081)
INTENT GFX.PRESENT()

The Intent API describes meaning. A 6502 runner can route an admitted request to a composed display head, math engine, bulk-memory provider, storage service, or other accelerator. A desktop runner may perform the same request through its own local route. D/BASIC code sees one operation, one ordering rule, and one result.

Acceleration is never a hidden second application. When several conforming routes exist, the runner selects the best available one. When no honest route can satisfy a required operation, admission or execution refuses by name rather than fabricating success.

Give the optimizer a big idea

A 6502 benefits most when source says what a whole operation means:

ARRAYFILL Back%(), 0
ARRAYCOPY Source%() TO Working%()
ARRAYSWAP Front%(), Back%()

These verbs give the compiler and runner a semantic unit that can be sliced, accelerated, or reduced locally. ARRAYSWAP exchanges two bindings in constant work; it does not walk either array. Pure floating-point expression trees can likewise become one ordered MATH request instead of a crossing per operator.

The developer-friendly rule is simple: write the clearest large operation. The toolchain handles placement and crossing reduction.

What still belongs to the programmer

Hidden banking does not mean imaginary resources. Good 6502-friendly D/BASIC still has a shape:

  1. Use the smallest honest numeric type for large collections.
  2. State whole-array work with ARRAYFILL, ARRAYCOPY, and ARRAYSWAP.
  3. Keep resource and network operations bounded and asynchronous.
  4. Repaint damaged regions instead of every unchanged control.
  5. Release arrays, objects, streams, and sessions when their ownership ends.
  6. Declare required capability families with APP ... NEEDS and query optional capabilities only when the application has two useful experiences to offer.
  7. Test the exact DBC on every intended target profile.

What you do not manage is just as important: bank layouts, resident trampolines, provider mailboxes, accelerator command packets, and restoration of the machine's mapping state. Those are runner contracts.

The promise in one line

ordinary D/BASIC → one DBC image → 6502 runner → hidden paging + semantic acceleration → the same observable program

A learner gets DIM, arrays, classes, and widgets. An experienced 6502 developer gets a real small-machine target without carrying three decades of manual memory machinery into every application. That is the point: the charm of the 6502 remains; its old application ceiling does not.


Next: Bytecode & Cross-Platform Delivery →