This commit is contained in:
roma-dxunvrs
2026-09-18 13:47:58 +03:00
parent 352278275c
commit fb5f9d4cf2
3 changed files with 90 additions and 1 deletions
@@ -0,0 +1,88 @@
- 32-битная стековая архитектура
- Главный принцип - команды не имеют операндов в коде. Все данные неявно берутся с веришны стека, результат кладется обратно в стек.
- Небольшая пасхалка на [[Evaluate Reverse Polish Notation|вот эту задачу с литкода]]
## Регистры, флаги и стеки
- Имеет два независимых аппаратных стека и два регистра-указателя
### Стеки
- `Data Stack` - место где происходят вычисления
- `T` - вершина стека данных
- `S` - второй элемент на стеке данных
- `Return Stack` - стек возврата
- `R` - вершина стека возвратов
### Регистры
- `A` - указатель адреса памяти с поддержкой автоинкремента
- `B` - второй указатель памяти
### Флаги
- `C` - перенос
- `EAM` - режим расширенной арифметики
> [!info] Все регистры, флаги и стеки можно выводить в отчете: `A:dec`, `stack:dec`, `rstack:hex`, `C`, `EAM` и так далее
> [!info] Получение максимальной глубины стека, которая была достигнута на конкретном шаге через: `f32a:data-stack-max` и `f32a:return-stack-max`
## Система команд
- Комментарии - `\`
- Опкод - 1 байт. Если есть операнд, то еще 4 байта
### 1. Операции со стеком
- `dup` - дублировать вершину (`[X] -> [X, X`])
- `drop` - удалить вершину (`[X, Y] -> [X]`)
- `over` - скопировать второй элемент наверх (`[X, Y] -> [X, Y, X]`)
### 2. Операции над памятью
- `<value>` (или `lit <value>`) - положить константу на `Data Stack` (от слова literal)
- `a!` / `b!` - снять значение со стека и записать в регистр `A` / `B`
- `a` - скопировать значение из регистра `A` на стек
- `@p <addr>` - прочитать из адреса на стек
- `!p <addr>` - снять со стека и записать по адресу
- `@` / `!` - чтение/запись по адресу, который лежит в регистре `A`
- `@+` / `!+` - чтеение/запись по адресу в `A` и инкремент `A`
- `@b` / `!b` - чтение/запись по адресу, который лежит в регистре `B`
### 3. Арифметика
- `+` - сумма, снимает два верхних числа, складывает, кладет на вершину стека. Если в режиме `EAM`, то прибавляет еще и флаг переноса. Устанавливает флаг переноса в любом случае
- `+*` - один шаг умножения
- `+/` - один шаг деления
- `2*` - сдвиг влево вершины стека
- `2/` - сдвиг вправо вершины стека
- `inv` - побитовая инверсия вершины стека
- `eam` - снять число с `Data Stack` и записать его в `EAM`
### 4. Побитовые операции
- `and` - побитовое И над `T` и `S` (снимает их)
- `xor` - исключающее ИЛИ
### 5. Управление вычислительным процессом
- `;` - возврат из подпрограммы (снять адрес с `Return Stack` и прыгнуть)
- `<label>` - вызов подпрограммы (адрес возврата кладется на `Return Stack` + прыжок)
- `<label> ;` - прыжок (без записи адреса возврата)
- `next <label>` - цикл for, проверяет вершину стека возврата `R`, если `R != 0`, уменьшает его на 1 и прыгает на метку. Если `R==0`, удаляет `R` и выходит из цикла
- `if <label>` - снимает значение с `Data Stack`, если оно ноль, то прыжок
- `-if <label>` - снимает значение с `Data Stack`, если оно `>=0`. то прыгает
- `halt` - стоп
### 6. Взаимодействие со стеком возврата
- `r>` - снять элемент с `Return Stack` и положить на `Data Stack`
- `>r` - снять элемент с `Data Stack` и положить на `Return Stack`
## Подробнее про умножение и деление
### `+*`
- Как работает умноежение чисел в двоичной системе (`A*S`): смотрим на каждый бит числа A, если бит 1, то прибавляем S к результату, если бит 0, то пропускаем, в конце сдвигаем результат
- Как использует эта команда данные
1. Регистр `A` - множитель
2. `S` (второй элемент стека данных) - множимое
3. `T` (первый элемент стека данных) - накапливается ответ, сначала 0
- Теперь эта команда по шагам
1. Процессор смотрит на младший бит `A`
2. Если там `1`, то прибавляет `S` к `T` (`T = T + S`). Если `0` - ничего не прибавляет
3. Сдвигает регистр `A` вправо на 1 бит (`A >> 1`). Тот бит, который мы только что проверили выбрасывается
4. Сдвигает результат `T` вправо на 1 бит (`T >> 1`)
5. Сдвинутый из `T` бит вставляется в старший бит `A` (`A[31]`)
- После 32 повторений такой команды, в регистре `A` и на вершине стека `T` образуется 64-битное число - полный результат умножения. В `A` - младшая половина ответа, а в `T` - старшая
### `+/`
- Деление в двоичной системе: сдвигаем влево и пытаемся вычесть делитель. Если вычитается - пишем 1, если нет - 0
- Как использует данные команда
1. Регистр `A` - делимое
2. `Mem[B]` - делитель
3. `S` - старшая часть числа, здесь будет остаток
4. `T` - кладем 0, здесь будет частное
- Команда по шагам
1. Сдвигаем остаток `S` влево на 1 бит
2. Забираем старший бит из `A` и вставляем в младший бит `S`
3. Сдвигаем `A` влево
4. Сдвигаем `T` влево
5. Если можно вычесть делитель (`S >= Mem[B]`), то ставим в младший бит ответа единицу (`T[0]=1`), если нельзя - бит ответа оставляем нулем
- После 32 повторений на стеке в `T` будет лежать частное, в `S` - остаток от деления.