Руководство по ассемблеру
Архитектура процессора
Если вы начинаете с нуля и хотите системный маршрут, начните со статьи «Как выучить ассемблер с нуля». На 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 и запускает тактирование; повторный клик ставит прогон на паузу. При ошибке компиляции показывается сообщение, выполнение не начинается.
- Редактор кода — текстовое поле с подсветкой синтаксиса, занимает остаток панели. Комментарии через
;. Слева от строк-инструкций выведены hex-адреса: клик по адресу ставит или снимает брейкпоинт (красная точка). Строки без инструкции (метки, комментарии) адреса не имеют. Правка кода после запуска приглушает адреса до следующей компиляции. - Журнал — раскрываемый лог с префиксами:
[Asm]— компиляция,[CPU]— ход выполнения,[HLT]— останов,[BP]— брейкпоинты и правки RAM. Сюда же попадают ошибки компиляции. - Справка — раскрываемая таблица команд и синтаксиса.
Отладчик
Блок отладчика показывает реальное состояние схемы — не модель, а те самые узлы PC, Register8 и RAM, которые вы соединили проводами.
- Регистры — строка под тулбаром: PC, ACC (аккумулятор), IX (индексный регистр), флаги Z и N, счётчик тактов. Обновляется на каждом тике. Если PC указывает за пределы программы, в статусе виден декод инструкции из живого ROM.
- ⏭ Шаг — исполняет одну инструкцию (полный тактовый цикл: фронт + спад). Между полушагами видна фаза: «Выборка» или «Исполнение». Так можно проследить программу команда за командой.
- ⟲ Сброс — обнуляет PC, ACC, IX и RAM, не трогая программу в ROM.
- RAM — раскрываемая сетка 256 ячеек; после первого «Выполнить» раскрывается сама, а если закрыть её вручную, до смены уровня она снова не откроется. Ненулевые ячейки выделены цветом, только что изменённые вспыхивают. Клик по ячейке — правка значения (можно десятичным числом или
0x..), двойной клик переключает формат hex/dec. Изменённое значение сразу видно на схеме. - Брейкпоинты — клик по hex-адресу слева от строки кода ставит или снимает точку останова; после «Выполнить» автопрогон остановится перед ней. Правка кода приглушает маркеры до перекомпиляции.
- Подсветка строки — текущая исполняемая строка кода подсвечивается в редакторе; прыжки (JMP/JZ) видны сразу.
- HLT — по инструкции останова выполнение паузируется, строка остаётся подсвеченной.
Типичный цикл отладки: «Выполнить» → программа остановилась не там → «Сброс» → «Шаг» до подозрительной команды → смотрим ACC и RAM → правим код → снова «Выполнить».
Формат инструкции
Каждая инструкция — 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 не сохраняются между сессиями — это сессионные инструменты отладки.
Смежные компоненты
Статьи по теме
Частые вопросы
Что делает ассемблер?
Переводит мнемоники — ADD, JMP, OUT — в машинные коды, которые понимает процессор.
Зачем учить ассемблер?
Чтобы понимать, как программы исполняются на самом нижнем уровне и что делает каждый такт процессора.