Ассемблер процессора i8080A на m4
Сегодня опять сычевал почти до полуночи — вечером попался кусок кода на m4 и я, пробормотав вечное «когда-нибудь надо бы его выучить», вдруг подумал — а почему бы и не сейчас? Я и так всё время откладываю, а этот вечер ничем для этой цели не хуже, чем любой другой.
Чтобы разбираться было веселее, придумал себе задачу — написать на m4 ассемблер, желательно не очень сложный, чтобы занятие на вечерок не разрослось потом до недельного проекта. Взял инструкции интеловского процессора 8080A — это был мой первый ассемблер. Советской аналог процессора стоял на компьютере «Радио-86РК», с которого я когда-то начинал.
Несколько слов о том что такое m4.
Это довольно простой макропроцессор, умеющий заменять одни указанные строки на другие. Описание что на что заменить делается параметрическими макросами. В языке есть собственные макросы, но можно определить и пользовательские. Поддерживаются так же условия и математика. В продвинутых версиях m4 есть даже циклы, но их в любой версии можно эмулировать рекурсией.
Парочка примеров того как выглядит его синтаксис:
dnl Макрос, заменяющий одну строку на другую
define(`WORLD', `KAZAN')dnl
dnl Макрос, оставляющий первый свой параметр как есть, а
dnl второй складывающий со значением другого макроса,
dnl если второй параметр не указан, его значение — единица
define(`__pr', `define(`__ip', eval(__ip + ifelse($2, `', 1, $2)))$1')dnlЧтобы реализовать задуманное, я взял список команд процессора с кодами — их там чуть меньше 250, если брать варианты с разными регистрами, и написал на Пайтоне два генератора.
Два, потому что мне нужно было как-то решить проблему расчёта адресов ссылок. Первая программа генерирует файл linker8080.m4, который этим и занимается. Адреса он на следующем этапе передаёт в виде макросов второму файлу — asm8080.m4, который делает основную работу — переводит ассемблер в машинные коды.
Сначала у меня всё было сделано в одном файле, но я быстро выяснил, что макросы распространяются сверху вниз, то есть определённые ниже макросы не влияют на текст выше. Если ссылка, для которой я подсчитал адрес, находится ниже точки вызова, я этот вызов уже не могу заменить.
Вот программа на этом ассемблере, в действии её можно посмотреть на скриншоте:
ORG(h1100)
LXI H, ADDR(TEXT)
CALL hF818
JMP hF86C
LABEL(TEXT)
BYTE(h48, h45, h4C, h4C, h4F, h20, h57, h4F, h52, h4C, h44, hD, hA, h0)Не слишком отличается от привычного вида, но отличия всё-таки есть.
Во-первых, числа должны быть только шестнадцатеричными. Часто так и бывает, но тут запись немного непривычная — hFF вместо более привычной FFh.
Во-вторых, многие вещи, не генерирующие код, выглядят чуть иначе — адрес запуска задаётся конструкцией ORG(…), метка — через LABEL(…), а её адрес получается через ADDR(…).
В-третьих, произвольные цепочки байт задаются не директивой DB, а конструкцией BYTE(…).
Результат можно увидеть у меня в репозитории.