How STELLAR is built
STELLAR is a virtual CPU written in raw 1802 machine code. It uses up to 32 KB of RAM: about 12 KB for the interpreter, a shared page for coordinating VMs, one small "core" of state per VM, and the rest for program code.
$Memory map
R1802 register roles
STELLAR commits the 1802's sixteen hardware registers to fixed jobs. (These are the host registers — not to be confused with a VM's sixteen virtual registers, which live in RAM.)
| Reg | Purpose |
|---|---|
| R0 | Reserved (aliases the DMA pointer) — not used |
| R1 | Return to the monitor — the exit vector, supplied by the host monitor rather than by STELLAR |
| R2 | 1802 hardware stack ($2FC0–$2FFF) |
| R3 | Main program counter (the interpreter) |
| R4 · R5 · R6 | SCRT call · return · link |
| R7 | Label-array pointer |
| R8 | VM program-block pointer (the virtual PC) |
| R9 | VM stack pointer (program + call/return) |
| RA | CIDP pointer |
| RC | VM core pointer (which core is running) |
| RB · RD · RE · RF | Temporary internal registers |
R1 is not free. The 1802 vectors interrupts through R1, but STELLAR has already spent it: the firmware ends a program with $02E0 SEX R2 / MARK / SEP R1, handing control back to whatever address the monitor left there. Nothing in the firmware ever loads R1, precisely because its value has to be the monitor's. That is also why enabling interrupts with EINT does not get you an interrupt handler — any interrupt that arrives goes to the monitor's exit vector. Servicing interrupts would mean moving the exit vector to another register first.
→SCRT — calls without a stack
The 1802 has no call instruction. STELLAR uses RCA's Standard Call and Return Technique: R3 is the program counter, and switching it with another register (via SEP) transfers control. R4 points at the CALL routine ($0340), R5 at RETURN ($0360), and R6 carries the link address. Every STELLAR CALL/RETURN opcode rides on this.
■The VM core ($3000, $3400, $3800, and $3C00 blocks)
Each VM's entire live state is a 256-byte block. This is exactly what the core visualizer renders. VM1's block starts at $3000; VM2/3/4 at $3400/$3800/$3C00.
| Offset | Field | Notes |
|---|---|---|
| +$00–$1F | Registers R0–RF | Sixteen 16-bit general purpose registers, high byte first |
| +$20–$21 | Accumulator (Acc) | The main 16-bit working value |
| +$28–$2B | Decimal Accumulator (DA) | 32-bit packed BCD |
| +$2C–$2E | DA Remainder | 3 bytes, left by a decimal divide |
| +$2F | DA sign / decimal point | $00 = positive |
| +$30 | Program Command Register | The opcode currently executing |
| +$31–$32 | Program Location Register | The VM's position in its program (virtual PC) |
| +$33–$36 | Command Counter | 4-byte count of opcodes executed |
| +$37 | EF Flag | ZD — Z: $00 normal, $01 invert; D: which EF line, 1–4 |
| +$38 | Input Port | $09–$0F |
| +$39 | Output Port | $01–$08 |
| +$3A | Input / Output Byte | The last byte read or written |
| +$3B | Memory mode | $00 absolute, $01 relative — relative is the reset default, and what almost every program wants |
| +$3C | Program Stack Location | Stack pointer, counts down from $FF |
| +$3D | Last PUSH size | How many bytes the last push placed on the stack |
| +$3E–$3F | Last Location | Used with labels |
| +$40 | Address Modifier | $01–$FF |
| +$41 | Memory Address Pointer Direction | $00, $01 or $02 |
| +$42–$43 | Random Number Register | The seed RAND advances |
| +$44 | Delay Counter | $00–$FF |
| +$45 | Test Flag | Set by the TEST opcodes; read by the branches |
| +$46 | Debug Flag | $00–$FF; selects the DEBUG display mode |
| +$47 | System Status Flag | $00 while a program runs, $0B once it ends |
| +$48 | Carry Flag | $01 means a carry or borrow occurred |
| +$49 | Overflow Flag | $01 when a result did not fit |
| +$4A | Hex/Decimal Flag | $00 = hex, $01 = decimal |
| +$4B | unused | Reads as $00; nothing in the firmware uses it |
| +$4C | Interrupt Flag | $00 disabled, $01 enabled — set by DINT and EINT, read by TESTIS |
| +$4D | Remaining Delay | What is left of a delay in progress |
| +$4E | ExitCode Register | The result the last opcode reported |
| +$4F | Error Register | $00 none; $07 divide by zero, $01 undefined label, $0F unimplemented opcode |
| +$50 | Breakpoint Register | Whether a breakpoint is armed |
| +$51 | Breakpoint Command | Stop when this opcode is about to run |
| +$52–$53 | Breakpoint Location | Stop when the program pointer reaches this address; the run loop compares it every opcode |
| +$54–$55 | Breakpoint Bytes | |
| +$56 | Q State | $00 off, $01 on — the 1802's single output bit |
| +$57 | Hardware Identification | $01 on an Elf2K |
| +$58 | Call/Return Stack Location | Stack pointer for CALL/RETURN, counts down from $7F |
| +$59 | VM number | $00–$04 |
| +$5A | VM Core Location | High byte of this VM's core page, $30–$3F |
| +$5B | VM Program Block | High byte of this VM's program block, $40–$7F — the base every relative address is resolved against |
| +$5C | VM CIDP Location | High byte of the CIDP, $2F |
| +$5D | VM Data Block Size | |
| +$5E | Interrupt Counter | The multitasking quantum countdown |
| +$5F | Interrupt Counter Max | How long each VM's turn lasts; $03 by default |
| +$60–$62 | Scratch Register A | Firmware workspace |
| +$63–$65 | Scratch Register B | Firmware workspace |
| +$66–$67 | Scratch Register C | Firmware workspace |
| +$68–$6B | Math Accumulator (MA) | 32-bit, most significant byte first |
| +$6C–$6D | MA Remainder | Left by a divide; read it with COPYREMA |
| +$6E | MA sign / decimal point | $00 = positive. Negative results are stored as a magnitude plus this byte, not two's complement |
| +$70–$CF | Temporary workspace | Firmware scratch |
| +$D0–$DF | Trace parameters | |
| +$E0–$FF | 1802 register copy | Where the real CPU's registers are parked across a VM switch |
Every field of the block, in address order. Offsets are from the start of the VM's core: add $3000 for VM1, $3400 for VM2, and so on.
⇄CIDP — the multitasking page ($2F00)
The Common VM Interchange Data Page is how the scheduler and the VMs coordinate. The first 64 bytes are a per-VM array (16 bytes each); the system registers at $2F50 hold the global scheduler state. This whole page is decoded live on the multitasking view.
| Address | Field | Meaning |
|---|---|---|
| $2F00+ | Per-VM array | VM#, core page, program page, max size, count, State, #Cmds, mode, status — 16 bytes × 4 VMs |
| $2F51 | VM Error | $00 none · $01 no VMs · $02 abort |
| $2F52 | Current VM# | Which VM holds the CPU right now |
| $2F53 / $2F54 | Max / Total VMs | Ceiling (4) and how many are present |
| $2F55 | Multitasking Allowed | $00 = off, $01 = on |
| $2F56 | VM Operation | $00 = sequential, $01 = simultaneous |
| $2F57 | VM Repeat | $00 = run to end, $01 = repeat |
VM State values: $00 done · $01 active · $02 completed this cycle · $03 idle/waiting · $04 paused · $05 killed · $06 stopped on error.
↻Cooperative multitasking
VMs are not pre-empted by a timer. Each active VM runs a small number of opcodes — the quantum, default three — then yields, and the scheduler (Do_Next_Task at $0080) scans the CIDP for the next VM in state $01. A VM waiting on serial input (SERIN) yields cooperatively instead of stalling everyone. Because all VMs share one UART, their terminal output interleaves — which the multitasking view makes visible.
Ready to see it move? Open the VM Core Visualizer to watch one core execute, or the opcode reference to look up any instruction.