88 lines
7.4 KiB
Markdown
88 lines
7.4 KiB
Markdown
- 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` - остаток от деления. |