Assembler Manual
Processor Architecture
Level 16 features an 8-bit Harvard-architecture computer with separate instruction and data memory:
- ROM (256 bytes) — instruction memory, read by the program counter (PC). Holds the compiled program.
- RAM (256 bytes) — data memory. Read/write via a 4-bit address (instruction operand).
- ProgramCounter — increments (+1) on each clock cycle (rising edge), overwritten on JMP/JZ.
- ALU8 — arithmetic logic unit: ADD, SUB, AND, OR on accumulator and RAM. Outputs a Zero flag.
- Register8 — accumulator, stores the current operation result.
- Clock — timing generator. One instruction cycle = 2 ticks (rising + falling edge).
- Decoder — combinational circuit (NOT/AND/OR), converts opcode to control signals.
Assembler Panel
When a ROM is present on the canvas, an «Assembler» tab appears in the right panel. It contains:
- Text editor — code editor. Comments via
;. - ▶ Execute — compiles the program to bytes, writes to ROM, resets PC to 0, and starts clocking. Compilation and execution logs appear in the debug panel. Compilation errors stop execution.
- Indicators — PC (program counter) and Instr (current instruction mnemonic). Updated every tick.
- Log panel — scrollable log with prefixes:
[Asm]— compilation,[CPU]— execution,[HLT]— halt. - Help — expandable command/syntax reference table below the log panel.
Instruction Format
Each instruction is 1 byte. Upper 4 bits (7:4) = opcode, lower 4 bits (3:0) = operand (address 0–15).
7 6 5 4 3 2 1 0
┌────────┬─────────┐
│ Opcode │ Operand │
└────────┴─────────┘
Instruction Set
| Mnemonic | Opcode | Action | Example |
|---|---|---|---|
| NOP | 0 | No operation | NOP → 0x00 |
| ADD | 1 | Acc = Acc + RAM[X] | ADD 3 → 0x13 |
| SUB | 2 | Acc = Acc − RAM[X] | SUB 7 → 0x27 |
| AND | 3 | Acc = Acc & RAM[X] | AND 1 → 0x31 |
| OR | 4 | Acc = Acc | RAM[X] | OR 2 → 0x42 |
| LDA | 5 | Acc = RAM[X] | LDA 5 → 0x55 |
| STA | 6 | RAM[X] = Acc | STA 10 → 0x6A |
| JMP | 7 | PC = X (unconditional jump) | JMP 0 → 0x70 |
| JZ | 8 | if Acc=0: PC = X (conditional jump) | JZ 7 → 0x87 |
| HLT | 15 | Stop clocking | HLT → 0xF0 |
Syntax Rules
- One instruction per line.
- Mnemonics are case-insensitive:
lda,LDAare the same. - Comments start with
;. Everything after it is ignored. - Empty lines and comment-only lines are skipped.
- Operand is a decimal number 0–15. Instructions with address > 15 will not compile.
- NOP and HLT do not require an operand — it is ignored.
Example: Compilation and Execution
; Simple program — 4 instructions
LDA 5
ADD 3
STA 0
HLT
After pressing ▶ Execute, the program compiles and immediately runs. The log panel shows compilation lines:
[AsmPanel] === Compile & Run ===
[Asm] ROM[0] = 0x55 (LDA 5)
[Asm] ROM[1] = 0x13 (ADD 3)
[Asm] ROM[2] = 0x60 (STA 0)
[Asm] ROM[3] = 0xf0 (HLT)
[Asm] Compile done: 4 instruction(s)
[Asm] ROM[0..3]: 0x55 0x13 0x60 0xf0
Then — execution lines (odd ticks only — rising edge):
[AsmPanel] PC=0, play
[CPU] Tick 1 PC=0 → LDA 5 [0x55]
[CPU] Tick 3 PC=1 → ADD 3 [0x13]
[CPU] Tick 5 PC=2 → STA 0 [0x60]
[CPU] Tick 7 PC=3 → HLT [0xf0]
[HLT] PC=4 (prev=3) instruction=0xf0 → HALT detected, stopping
Important: RAM Initialization
LDA 5 loads the value from RAM[5], not the constant 5. Since all RAM cells are initially zero, the accumulator after LDA 5 will be 0. ADD 3 adds RAM[3] = 0. The result is always 0.
For meaningful data work, initialize RAM via the browser console (F12 → Console) before pressing «Execute»:
const ram = graph.findNodesByType('tc/RAM')[0];
ram.memory[5] = 5; // RAM[5] = 5
ram.memory[3] = 3; // RAM[3] = 3
After initialization and execution of LDA 5; ADD 3; STA 10; HLT, RAM[10] will contain 8.
Example: Working with Data
; Load value from RAM[10], double it, store in RAM[11]
LDA 10 ; Acc = RAM[10]
ADD 10 ; Acc = Acc + RAM[10] = RAM[10] * 2
STA 11 ; RAM[11] = Acc
HLT
Example: Conditional Jump (JZ)
; JZ test: if accumulator = 0 → jump to address 5
LDA 0 ; Acc = RAM[0] = 0
ADD 0 ; Acc = 0 + 0 = 0 (Zero flag = 1)
JZ 5 ; Jump to addr 5 (triggered)
STA 15 ; ← skipped
HLT ; ← skipped
NOP ; address 5 — jump target
HLT ; stop here
Reading Memory via Console
// Check RAM contents after program execution
const ram = graph.findNodesByType('tc/RAM')[0];
console.log('RAM[10]:', ram.memory[10]);
console.log('RAM[11]:', ram.memory[11]);
// Check ROM contents after compilation
const rom = graph.findNodesByType('tc/ROM')[0];
const hex = Array.from(rom.memory.slice(0, 8))
.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' ');
console.log('ROM[0..7]:', hex);
How an Instruction Executes (2 Ticks per Command)
Each instruction takes exactly two Clock ticks. The first (rising edge 0→1) fetches and executes, the second (falling edge 1→0) latches the result:
Tick N (0→1): PC outputs address → ROM → Splitter → Decoder → ALU/RAM
Result is ready but not yet written to registers.
Tick N+1 (1→0): Registers capture data. PC does not change.
This is why the log only shows instructions on odd ticks (1, 3, 5, …) — the rising edge when the instruction actually executes.
Log Panel Prefixes
| Prefix | When it appears |
|---|---|
[Asm] | Compilation: each instruction and final ROM dump |
[AsmPanel] | Pressing ▶ Execute — compilation and launch stages |
[CPU] | Every odd tick — executing instruction |
[HLT] | HALT detected — clocking stopped |
The log clears on each ▶ Execute press before compilation. History is not preserved between runs.
Example: Unconditional Jump (JMP)
; Infinite loop between addresses 2 and 3
LDA 0
STA 15
JMP 2 ; ← loop back to JMP
HLT ; never executes
The program will loop — HLT never triggers. Press ⏸ Pause on the Clock Control panel to stop. Verify in the log that PC loops on 2.
Example: Data Tracing
With RAM[10] = 7 (set via console):
LDA 10 ; Acc = RAM[10]
ADD 10 ; Acc = Acc + RAM[10] = RAM[10] * 2
STA 11 ; RAM[11] = Acc
HLT
| Tick | PC | Instruction | Acc | RAM[10] | RAM[11] |
|---|---|---|---|---|---|
| 1 | 0 | LDA 10 | 7 | 7 | 0 |
| 3 | 1 | ADD 10 | 14 | 7 | 0 |
| 5 | 2 | STA 11 | 14 | 7 | 14 |
| 7 | — | HLT | — | — | — |
Shortcut Keys
| Key | Action |
|---|---|
F9 | Run level verification |
F12 | Open developer console |
Delete | Delete selected element |
Known Limitations
- No immediate values.
LDA 5means "load from RAM[5]", not "load constant 5". - Operand 0–15 — only a small portion of memory is accessible. The instruction format allocates 4 bits for the operand (addresses 0–15). This means:
- RAM: LDA/STA/ADD/SUB/AND/OR only work with cells 0–15. Cells 16–255 (240 bytes) are inaccessible via instructions — only through the browser console. For educational programs, 16 cells is sufficient.
- ROM (JMP/JZ): jumps are only possible to addresses 0–15. Linear execution (PC +1) works across the full ROM (0–255), but loops and branches can only target the first 16 cells.
- No labels. JMP/JZ take absolute ROM addresses. Adding/removing instructions shifts addresses — update jumps manually.
- F9 check is structural. The F9 key on level 16 only verifies that required components (PC, ROM, RAM, ALU8, Clock) are present and that 5 steps run without crashing. Functional correctness of assembly programs is not checked — debugging is entirely on the player via the log panel.
- Manual RAM debugging. To check results after HLT, use the browser console (F12).