На прошлой неделе я разбирал Emulator Exidy Sorcerer, что подтолкнуло меня написать версию декомпрессора LZ4 на процессоре Z80. Ранее этот же алгоритм реализовывался на NES (SNES), CoCo и Genesis. Код стал настолько универсальным, что я вынес реализации в отдельную библиотеку для других проектов, кроме реализации под SNES, которая была слишком привязана к конкретному проекту. Покопавшись в коде, я обнаружил, что версий шест — не три, а шесть: Z80 породил адаптации для Intel 8080 и Intel 8086, а отсутствие версии под 6502 я восполнил отдельно.
Изначально план был сравнить четыре процессора в контексте их архитектуры, но работа вышла слишком объёмной, поэтому статья разбита на две части. В этой я сравниваю процессоры, а на следующей неделе покажу, как различия влияют на код на практике. Z80 — прямой наследник 8080 и его бинарно совместимый апгрейд. Это значит, что 8080 можно считать упрощённой версией Z80. Упрощения очевидны: нет инструкций для относительных прыжков (JR, DJNZ), отсутствуют тень регистров (команды EX AF,AF', EXX) и всё многобайтовые инструкции. Отказ от последних лишает нас индексных регистров и прямого доступа к портам ввода-вывода. Однако условные возвраты и вызовы сохранились, так как у 8080 не хватило свободных кодов для других вариантов.
Синтаксис ассемблера 8080 напрямую соответствовал бинарному коду, тогда как Zilog использовал описательную нотацию. На Z80 команда INC объединила несколько функций, а LD поглотила огромное множество инструкций 8080. Синтаксический регистр M в 8080 превратился в (HL) на Z80, где добавлена поддержка индирации через (BC) и (DE). Инструменты вроде Pasmo с флагом -w8080 помогают писать код для 8080 через Z80 ассемблер.
Intel 8086 вышел двумя годами после Z80 и является полноценным 16-битным чипом. В отличие от 8080 с его парами регистров, 8086 вводит разделённые регистры AH/AL, BH/BL и CH/CL. Доступ к памяти расширен до 1 МБ через сегментацию с регистрами CS, DS, ES, SS. Система сегментов, хотя и критикуется, удобнее систем банкинга 65816: два сегментных регистра данных (DS, ES) позволяют работать с двумя буферами одновременно. З80 также взял универсальную копию семейств инструкций LDIR, где SI и DI выступают как HL и DE, а CX служит счётчиком.
Для разработки под 8086 лучше всего подходит NASM, так как он проще и не привязан к устаревшим форматам. Motorola 6800 и его наследник 6809 — другая история. 6809 предлагает два накопителя A и B, которые можно объединить в 16-битный D. Он также имеет индексные регистры X и Y, второй работает с полным 16-битным пространством адресов. Это позволяет писать позиционно-независимый код. Лучшим усовершенствованием 6809 стали именно эти регистры и возможность использовать указатели на стек как базовые регистры.
6502 — принципиально иной дизайн. Здесь нет 16-битных регистров, указатели проходят через «страницу нуля», а стек ограничен диапазоном $0100–$01FF. Концепция «sixers» (любителей 6502/6809) против «eighters» (8080/Z80) всё ещё актуальна. На 6502 вычисления лучше строить вокруг двух-адресного кода в памяти, а на Z80 данные должны находиться в регистрах или доступных через указатели. Стек на 6502 слаб и подходит только для кэширования временных значений, тогда как Z80 и 8086 лучше умеют работать с временными переменными. На 6809 уже можно строить полноценные фреймы стека.
Рекомендация по 6502 — держать переменные цикла в регистре Y, а на Z80 — использовать пары HL, DE, BC для указателей и счёта. Заметьте, что код для Z80 часто быстрее и совместим с 8080, если избегать сложных инструкций, доступных только Z80, таких как обмен теньми регистров EX AF,AF' и EXX. Быстрыми остаются однобайтовые инструкции, работающие только с регистрами. На следующей неделе я применим эти теории на практике, запуская один и тот же алгоритм на четырёх процессорах одновременно.