Введение

Я изучаю ассемблер 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 г.