Руководство по ассемблеру
Архитектура процессора
На 16-м уровне вы собираете 8-битный компьютер по гарвардской архитектуре с раздельной памятью команд и данных:
- ROM (256 байт) — память инструкций. Читается счётчиком команд (PC). Содержит скомпилированную программу.
- RAM (256 байт) — память данных. Чтение/запись по адресу (4-битный операнд инструкции).
- ProgramCounter — счётчик команд. Инкрементируется (+1) на каждом такте (по фронту Clock), либо перезаписывается при JMP/JZ.
- ALU8 — арифметико-логическое устройство: ADD, SUB, AND, OR над аккумулятором и RAM. Выдаёт флаг Zero.
- Register8 — аккумулятор. Хранит результат текущей операции.
- Clock — тактовый генератор. Один цикл инструкции = 2 тика (фронт + спад).
- Decoder — комбинационная схема (NOT/AND/OR), преобразует opcode в управляющие сигналы.
Панель ассемблера
При наличии ROM на схеме в правой панели появляется вкладка «Ассемблер». Она содержит:
- Текстовое поле — редактор кода. Комментарии через
;. - ▶ Выполнить — компилирует программу в байты, записывает в ROM, сбрасывает PC в 0 и запускает тактирование. Лог компиляции и выполнения выводится в панель отладки. При ошибке компиляции показывается сообщение, выполнение не начинается.
- Индикаторы — PC (счётчик команд) и Инстр (мнемоника выполняемой инструкции). Обновляются на каждом тике.
- Панель лога — скроллируемый журнал с префиксами:
[Asm]— компиляция,[CPU]— ход выполнения,[HLT]— останов. - Справка — раскрываемая таблица команд и синтаксиса под панелью лога.
Формат инструкции
Каждая инструкция — 1 байт. Старшие 4 бита (7:4) — opcode, младшие 4 бита (3:0) — operand (адрес 0–15).
7 6 5 4 3 2 1 0
┌────────┬─────────┐
│ Opcode │ Operand │
└────────┴─────────┘
Система команд
| Мнемоника | Opcode | Действие | Пример |
|---|---|---|---|
| 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 (безусловный переход) | JMP 0 → 0x70 |
| JZ | 8 | if Acc=0: PC = X (условный переход) | JZ 7 → 0x87 |
| HLT | 15 | Останов тактирования | HLT → 0xF0 |
Правила синтаксиса
- Одна инструкция на строку.
- Мнемоника регистронезависима:
lda,LDA— одно и то же. - Комментарии начинаются с
;. Всё после символа игнорируется. - Пустые строки и строки-только-комментарии пропускаются.
- Операнд — десятичное число 0–15. Инструкции с адресом > 15 не скомпилируются.
- NOP и HLT не требуют операнда — он игнорируется.
Пример программы: компиляция и выполнение
; Простейшая программа — 4 инструкции
LDA 5
ADD 3
STA 0
HLT
После нажатия ▶ Выполнить программа компилируется и сразу запускается. В панели лога появятся строки компиляции:
[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
Затем — строки выполнения (только нечётные тики — 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
Важно: инициализация RAM
LDA 5 загружает значение из RAM[5], а не константу 5. Поскольку вся RAM при старте заполнена нулями, аккумулятор после LDA 5 будет равен 0. ADD 3 добавит RAM[3] = 0. Результат всегда 0.
Для осмысленной работы с данными инициализируйте RAM через консоль браузера (F12 → Console) перед нажатием «Выполнить»:
const ram = graph.findNodesByType('tc/RAM')[0];
ram.memory[5] = 5; // RAM[5] = 5
ram.memory[3] = 3; // RAM[3] = 3
После инициализации и выполнения LDA 5; ADD 3; STA 10; HLT в RAM[10] будет записано 8.
Пример: работа с данными
; Загрузить значение из RAM[10], удвоить, сохранить в RAM[11]
LDA 10 ; Acc = RAM[10]
ADD 10 ; Acc = Acc + RAM[10] = RAM[10] * 2
STA 11 ; RAM[11] = Acc
HLT
Пример: условный переход (JZ)
; Тест JZ: если аккумулятор = 0 — переход на адрес 5
LDA 0 ; Acc = RAM[0] = 0
ADD 0 ; Acc = 0 + 0 = 0 (Zero flag = 1)
JZ 5 ; Jump to addr 5 (срабатывает)
STA 15 ; ← пропускается
HLT ; ← пропускается
NOP ; адрес 5 — точка перехода
HLT ; останов здесь
Чтение памяти через консоль
// Проверить содержимое RAM после выполнения программы
const ram = graph.findNodesByType('tc/RAM')[0];
console.log('RAM[10]:', ram.memory[10]);
console.log('RAM[11]:', ram.memory[11]);
// Проверить содержимое ROM после компиляции
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);
Как выполняется инструкция (2 тика на команду)
Каждая инструкция занимает ровно два такта Clock. На первом (фронт 0→1) происходит выборка и выполнение, на втором (спад 1→0) — фиксация результата:
Тик N (0→1): PC выдаёт адрес → ROM → Splitter → Decoder → ALU/RAM
Результат готов, но ещё не записан в регистры.
Тик N+1 (1→0): Регистры захватывают данные. PC не меняется.
Именно поэтому в логе инструкции отображаются только на нечётных тиках (1, 3, 5, …) — это rising edge, когда инструкция реально выполняется.
Что пишется в лог-панель
| Префикс | Когда появляется |
|---|---|
[Asm] | Компиляция: каждая инструкция и итоговый дамп ROM |
[AsmPanel] | Нажатие ▶ Выполнить — этапы компиляции и запуска |
[CPU] | Каждый нечётный тик — выполняемая инструкция |
[HLT] | Обнаружение HALT — тактирование остановлено |
Лог очищается при каждом нажатии ▶ Выполнить перед компиляцией. История между запусками не сохраняется.
Пример: безусловный переход (JMP)
; Бесконечный цикл между адресами 2 и 3
LDA 0
STA 15
JMP 2 ; ← возврат на JMP (цикл)
HLT ; никогда не выполнится
Программа зациклится — HLT не сработает. Нажмите ⏸ Pause на панели Clock Control для остановки. В логе убедитесь, что PC зацикливается на 2.
Пример: трассировка данных
При RAM[10] = 7 (установлено через консоль):
LDA 10 ; Acc = RAM[10]
ADD 10 ; Acc = Acc + RAM[10] = RAM[10] * 2
STA 11 ; RAM[11] = Acc
HLT
| Тик | PC | Инструкция | Акк | 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 | — | — | — |
Горячие клавиши
| Клавиша | Действие |
|---|---|
F9 | Запустить проверку уровня |
F12 | Открыть консоль разработчика |
Delete | Удалить выделенный элемент |
Известные ограничения
- Нет непосредственных значений (immediate).
LDA 5означает «загрузить из RAM[5]», а не «загрузить константу 5». - Операнд 0–15 — доступна только малая часть памяти. Формат инструкции отводит под операнд 4 бита (адреса 0–15). Это значит:
- RAM: LDA/STA/ADD/SUB/AND/OR работают только с ячейками 0–15. Ячейки 16–255 (240 байт) недоступны через инструкции — только через консоль браузера. Для учебных программ 16 ячеек достаточно.
- ROM (JMP/JZ): переходы возможны только на адреса 0–15. Линейное выполнение (PC +1) работает по всей ROM (0–255), но для циклов и ветвлений доступны только первые 16 ячеек.
- Нет меток (labels). JMP/JZ принимают абсолютный адрес ROM. При добавлении/удалении инструкций адреса сдвигаются — правьте переходы вручную.
- Проверка F9 — структурная. Клавиша F9 на уровне 16 проверяет только наличие компонентов (PC, ROM, RAM, ALU8, Clock) и 5 шагов без краша. Функциональная корректность ассемблерных программ не проверяется — отладка полностью лежит на игроке и панели лога.
- Ручная отладка RAM. Для проверки результатов после HLT используйте консоль браузера (F12).