Введение
Я изучаю ассемблер Z80 с намерением собрать свой собственный компьютер на базе Z80, а также научиться писать игры для ZX Spectrum. Два кажутся хорошими проектами, которые хорошо сочетаются друг с другом. В этом посте я сравниваю сходства и различия между языком программирования низкого уровня, таким как Z80, и языком высокого уровня, таким как C.
Типы языков программирования
Существует много языков программирования, но их обычно можно разделить на два основных широких типа.
Языки высокого уровня
Они немного похожи на английский, обычно используют слова для выражения концепций программирования и понятны людям. Если вы написали код на Python, JavaScript или C, вы написали его на языке высокого уровня.
Языки низкого уровня
Это прямой перевод 1:1 двоичного кода, который понимает ЦП, и используются короткие мнемоники для перечисления шагов, необходимых для выполнения ЦП операций. Из-за низкоуровневой природы их трудно понять людям, но только с некоторыми низкоуровневыми языками перевода именно то, что выполняет ЦП.
(Да, иногда мы называем C языком среднего уровня, потому что это более низкий уровень, чем Python, но не такой низкий, как ассемблер. И да, вы можете смешивать ассемблер с некоторыми языками, такими как C. Мы не говорим о что сегодня…)
Конструкции программирования
Программирование компьютера делится на три основных типа операций.
- Последовательность — это просто то, где каждая инструкция выполняется последовательно, одна за раз.
- Выбор — здесь программа может принять решение и переключиться на другой фрагмент кода.
- Итерация. Итерация означает повторение, поэтому код зацикливается.
Вы также можете добавить подпрограммы в список.
Наша примерная программа
Вот знакомая маленькая программка, любой, у кого есть DVD-проигрыватель, видел что-то подобное
Как это работает
Код для этого относительно прост, если вы знаете хитрость. Нет, он не использует какой-то причудливый физический движок, он использует очень простую логику.
- Сохраните положение X и Y точки
- Сохраните вектор скорости X и Y
- Рассчитайте новое положение X и Y, добавив соответствующий вектор скорости
- Если позиция X или Y находится за пределами экрана, измените соответствующий вектор
- Повторение
C-версия
В C код будет выглядеть примерно так
#include <stdio.h>
#include <fake-graphics-lib.h>
#define SCREEN_W 100
#define SCREEN_H 100
int xPos, yPos, xVel, yVel;
int main()
{
xPos = 10;
yPos = 10;
xVel = 1;
yVel = -1;
while (1) {
if (xPos + xVel > SCREEN_W) xVel = -1;
if (xPos + xVel < 0>) xVel = 1;
if (yPos + yVel > SCREEN_H) yVel = -1;
if (yPos + yVel < 0>) yVel = 1;
xPos += xVel;
yPos += yVel;
clear_screen();
draw_at(xPos, yPos, "*");
wait_a_bit();
}
}
Даже если вы никогда раньше не встречались с C, он состоит из удобочитаемых слов, таких как «если» и «пока», и структурирован таким образом, что позволяет нам следить за ходом кода, используя отступы и фигурные скобки.
Ассемблерная версия Z80
Версия Z80 выглядит так
; Makes a * bounce around the screen
; Code file
start: .org #8000
.model Spectrum48
ld a,2
call 5633
ld a,1
ld (xPos),a
ld a,10
ld (yPos),a
ld a,1
ld (xVel),a
ld a,-1
ld (yVel),a
Loop:
; Motion on X Axis
ld a, (xPos)
ld hl,(xVel)
add a,l
cp 32 ; hit rh edge?
jp z,decX
cp 0 ; hit lh edge?
jp z,incX
MoveX:
ld a,(xPos)
ld hl,(xVel)
add a,l
ld (xPos),a
; Motion on Y Axis
ld a, (yPos)
ld hl,(yVel)
add a,l
cp 20 ; hit bottom edge?
jp z,decY
cp 0 ; hit top edge?
jp z,incY
MoveY:
ld a,(yPos)
ld hl,(yVel)
add a,l
ld (yPos),a
call print
jp Loop
decX:
ld a,-1
ld (xVel),a
jp MoveX
incX:
ld a,1
ld (xVel),a
jp MoveX
decY:
ld a,-1
ld (yVel),a
jp MoveY
incY:
ld a,1
ld (YVel),a
jp MoveY
print:
call setxy
ld a,'*'
rst 16
call delay
call setxy
ld a,32 ; ASCII code for space.
rst 16 ; delete old asterisk.
call setxy ; set up our x/y coords.
ret
setxy:
ld a,22
rst 16
ld a,(yPos)
rst 16
ld a,(xPos)
rst 16
ret
delay:
ld b,1
delay0:
halt
djnz delay0
ret
xPos defb 0
yPos defb 0
xVel defb 0
yVel defb 0
(Отказ от ответственности, это была моя первая «правильная» программа для Z80, поэтому не используйте ее в качестве примера того, как выглядят хорошие методы программирования!)
Первое, что вы должны были заметить, это его длина! Каждая инструкция идет по отдельной строке, и каждая инструкция выполняет одну конкретную операцию. На самом деле, некоторые из этих инструкций не делают ничего, кроме сохранения числа в части ЦП, называемой «регистром», готовой для того, чтобы ЦП переместил это число в часть ОЗУ для хранения.
Я не буду повторять все это, оставив читателю в качестве упражнения, но давайте возьмем один простой на вид фрагмент кода из C-версии.
Эта единственная строка кода просто добавляет содержимое «xVel» в переменную с именем «xPos». На некоторых других языках это может быть записано как «xPos = xPos + xVel».
В ассемблерном коде это выглядит так. Я добавил комментарии, чтобы объяснить, что делает код
(ЦП Z80 ограничен в том, как работают его регистры, вы не можете просто загрузить любую ячейку памяти в любой регистр, вы должны использовать определенные регистры, а некоторые регистры, такие как «hl», на самом деле являются двумя меньшими регистрами «h» и «l ', доступ к которым можно получить по отдельности или вместе, в зависимости от того, что вам нужно.)
Хотя код гораздо более подробный, вы можете видеть, что он очень точно объясняет ЦП, как сложить два числа вместе. Вот как это (насколько мне известно) делается, это самый простой способ описать «сложение двух чисел вместе» на компьютере. Это занимает четыре шага.
- Получите первое число по памяти, сохраните его
- Получите второе число по памяти, сохраните его тоже
- Сложите их вместе и запомните ответ
- Запишите ответ обратно в память
Именно этот прямой контроль над процессором делает язык ассемблера таким быстрым и таким интересным для написания. Это также то, что делает его таким сложным, многословным и разочаровывающим в отладке. И именно из-за этих негативных моментов существуют языки высокого уровня. Однако ЦП понимают только языки низкого уровня, поэтому под современным веб-браузером, работающим на JavaScript, по-прежнему находится инструмент перевода, который преобразует код в то, что может следовать ЦП.
Что-то, что нужно попробовать, — это шаблон переходов и вызовов функций в приведенном выше коде. Такие слова, как «Loop:» и «delay0:» в строках сами по себе являются метками. Команды «jp», «djnz» и «call» вызывают переход программы в новый раздел, а «ret» заставляет выполнение программы вернуться к последнему месту ее вызова.
Несомненно, существует и гораздо более короткий способ написания этого кода. Некоторые добавления скоростей кажутся излишними. В будущем посте я могу узнать, сколько циклов ЦП требуется для запуска кода, чтобы посмотреть, смогу ли я оптимизировать программы, чтобы они работали быстрее или использовали меньше памяти. На данный момент я просто пытаюсь научить свой мозг разбивать концепции высокого уровня, такие как циклы for, на их фактические составные шаги.
Первоначально опубликовано на https://ncot.uk 5 января 2020 г.