«откуда инфа? что именно с бета-диском так происходит?»
Я из таких лоскутов это собирал что уже не вспомню, иногда просто форумы какие то. Если это точно неправда, то надо исправить и тут. Прошу проверить даже такой момент — есть ли точки входа где байты в оригинальном пзу ненулевые.
Попробовал так сделать — ассемблер ругается что символ неопределён. Логично, ведь ini-файл есть параметр в линкер, но не в ассемблер. А если сообщаешь через .global что есть символ который потом пытаешься положить в .byte, то ругается что range error. Опять таки логично, ведь символы это адреса как правило и обойти можно сделав <NAME, т.е. взять младший байт и видимо в простой ситуации как PRG_BANKS уже сработало бы, но я попытался воткнуть это в вычисление по битовым маскам
byte MIRRORING | (HAS_SRAM << 1) | ((MAPPER & $0F) << 4) | NAME
и тут линкер выдал ошибку «Attribute expected» которая даже не гуглится особо да и я не понимаю как линкер может такое вообще отрабатывать нормально. Если только весь байт сразу там и определить разве что, но есть ли там битовые операции?
Нда, сам не заметил как скатился к полному разжёвыванию каждого нового встреченного ключевого слова и объяснения как байты лежат в памяти, так что заявление из первой статьи о том, что я не буду учить как программировать в ассемблере и архитектуре 6502 вообще пошло ломанными трещинами и начало рассыпаться в пух и прах.
Уже даже не пойму какая аудитория выйдет идеальной для этого всего, уже даже закрадывается что как тот PHP-шник который стал делать выступления на конференциях как он стал писать на NES за месяц. :D
P.S.
А ведь если задуматься, то такой подход (как у SjAsm) ничем не мешает и созданию кросс-платформенных форматов — генерацию каких то заголовков и служебных вещей тоже легко положить на какие то макросы из SAVEBIN OUTPUT_FILE которые обязательно надо выполнить в оговоренном порядке. В общем то дело техники. И у меня возникает подозрение, что FASM так и работает, поэтому конкретные форматы конкретных PE и COFF там дело библиотечного уровня.
В SjAsmPlus код в банках размещается как раз ORG'ами в одни и те же адреса и сохраняется в бинарники по мере трансляции, т.е. в те моменты, когда в нужных адресах виртуальной памяти сгенерирован нужный код, а дальше по ходу трансляции он может быть перезаписан.
О, кстати, вчитался и да — это то что мне было непонятно, вчера уже сонный был, так что цитата выше немного не по делу и касается только первой части. Получается что путало само наличие директивы PAGE X, и это воспринималось как раз как то наподобие сегментации, но непонятно как работающее в рамках многостраничных моделей. А это просто как бы переключение виртуальной памяти по схеме реальных 128Кб для возможных упрощений в каких то моментах, но сбрасывать на диск всё-равно надо постранично в момент активации нужных страниц.
Теперь и с SjAsmPlus всё понятно, спасибо.
В целом такой же «сегментированный» модульный и высокоуровневый подход не редкость и в ассемблерах и активно использовался и в программировании на Intel 8086, так что наверняка для многих из вас открытием он не будет. Но тем кто привык под «модулями» понимать просто включаемые в основной файл директивой INCLUDE тексты цельной программы компилируемые в один проход — тем надо немного переучится.
CA65/CC65 — пакет не для конкретно NES, а для любых систем на 6502
Нигде я не говорил что CC65 это только для NES, более того в начале этой части статьи написано про любой MOS 6502, но как то абстрактно и действительно стоит обозначить что cc65 имеет широкий охват. Это стоит сделать еще в предыдущей части. Действительно.
Также бывают ситуации, когда часть памяти кода переключаемая (разная память в одних и тех же адресах, т.е. банки)
Ни видел упоминаний сегментов в нескольких серьёзных исходниках, например Mighty Final Fight (под SjAsmPlus), но я тут реально слабо понимаю в том как именно в таком коде метки привязываются к переключению страниц.
У меня сложилось ощущение что просто через пару еще других ключевых слов и, вероятно, привязанных к виртуальным образам самого спектрума. Вообще не уверен.
Но разбираясь с секциями MEMORY и SEGMENTS ini-файла линкера cc65 очень быстро и легко понял как оно должно ложится на мапперы и любую структуру памяти любой машины. Это действительно сложно на первый взгляд, но понять нетрудно как и все выгоды — на самом деле даже очень просто.
Т.е. как переключать банки памяти на CA65 я понял даже быстрее чем на SjAsmPlus несмотря на то что со вторым столкнулся намного раньше.
Забавно, ведь во втором это сделано явно проще и для конкретной платформы, но глядя в документацию я не смог понять как.
А в CC65 просто поглядел пару минут в файл .ini с описаниями кусков памяти и сегментов и понял всё очень быстро.
Дальше только уточнения насчёт align и т.п. из официальной документации. И сразу понял кристально как сюда встраивать переключения банков.
Действительно вроде и сложнее, но по факту много проще.
У меня есть такая проблема — я вырос в детстве на ZX Spectrum 48 (конкретно отечественном клоне Кворум-64), но именно в детстве не стал в это деле опытным и начитанным ассемблерщиком и сейчас возвращаясь к этому и восторгаясь тем или иным вещам просто вынужден описывать как можно более подробно, вплоть для самого себя, с чем сталкиваюсь, причём опять и снова, но уже наученный про всё.
Вероятно действительно выходит ну очень подробно и с остановкой на каждом шаге. Надеюсь так и есть.
«As far asI know, there are no other tutorials for cc65 (not counting the example games over at Shiru’s site.)»
Воу, круто.
Тут еще на выходных пообщался с Кристофером который делает IDE Nesicide — он сейчас оказывается учавствует в джеме (сделать игру на NES за 48 часов) где вот: globalgamejam.org/2020/games/super-city-mayor-3
Credits:
Music by Shiru from the Famitone library.
Хехехе, первая тестовая программа с обильными комментариями на русском готова и крутится в эмуляторе:
Так что скоро наверное будет вторая часть. Правда таки разрываюсь на тему того не сделать ли сперва одну часть краткого введения в ассемблер и архитектуру MOS 6502 чтобы статья в целом лучше заходила тем кто в этом ни бум-бум изначально, а так, из Си пришёл. Хм…
Аллилуйя! Автор Nesicide наконец то устал с чем то там разбираться на своей стороне и добавил поддержку utf-8 и убрал дурацкое насильное автодополнение единственного оставшегося выбора в списке автодополнений (последнее вообще съедало мозг). Если кто-то из любопытства качал сборку для виндовс из-за этого поста, то обязательно перекачайте архив и зайдите в настройки среды — там настройки редактора и поставьте Encoding в utf-8.
Еще у меня есть вопрос к старожилам ресурса — недавно развлекаясь с нереализованными в детстве идеями на ZX Spectrum я написал еще две статьи на менее профильном ресурсе: Перехват прерываний на ZX Spectrum
и как бы её продолжение: Многопоточность на ZX Spectrum
Я знаю что про перехват прерываний не писал только ленивый и возможно это уже просто моветон. Про многопоточность вроде меньше материалов — поэтому вопрос: стоит ли тащить что либо из этого сюда?
:) Я, кстати, немного сравнивал Z8000 с i8086 по разным возможностям и в целом — забавно.
Как уже написано у Z8000 16 РОН и 8Мб сегментируемой памяти. При этом сегменты сделаны еще круче, чем в i8086 — вместо того чтобы держать отдельные сегментные регистры при сегментированной памяти адресация памяти через регистры автоматически подразумевает, что в чётных регистрах содержится номер сегмента (в верхнем байте), а в нечётных — смещение в сегменте. Т.е. косвенная адресация через регистры возможна с восемью 32-битными регистровыми парами 16-битных регистров R0-R15 которые уже называются RR0-RR14 (и могут быть только чётными номерами). Объединение в регистровые пары — характерная черта i8080/Z80 которая в Z8000 присутствует вплоть до 64-битных регистров RQ0,RQ4,RQ8,RQ12 видимо для операций длинного деления — разбираться не стал.
Дальше-больше. Если в i8086 подытожить все формы адресации, то их можно выразить самой комплексной формой:
[bx|bp+di|si+offset]
Т.е. или bx или bp сложенный с или di или si плюс смещение, при этом любая часть могла быть выкинута из выражения.
А вот в Z8000 самая сложная адресация возможная в любой команде — это [Rx+offset], где Rx это любой из шестнадцати его регистров кроме R0. А самая сложная адресация [Rx+Ry] где так же Rx и Ry это любые регистры кроме R0 возможна только в операциях передачи данных. С одной стороны вроде и менее сложные адресации, но с другой стороны этого более чем достаточно для эффективной адресации стековых переменных, а с другой стороны нет ограничений никаких какие именно регистры могут выступать в роли индексных, так что всё это немного похоже на современные RISC по возможностям.
Еще на англовики можно прочитать, что Z8000 неоднократно использовался в машинах на базе Unix. Чем же он был для них так привлекателен по сравнению с i8086? А вот чем:
Дело в том, что у Z8000 был дополнительный чип в _возможной_ обвязке — Z8010 именуемый еще Memory Managment Unit или MMU.
Так как сам Z8000 (точнее первая модель — Z8001) уже имел сегментированную память на 23 бита (8Мб) состоящую из 128 сегментов по 64Кб, то наличие MMU вроде бы смущает — казалось бы зачем?
Так вот как раз это самое — обеспечивать аппаратную защиту памяти read-only, code-only, data-only и system сегментов.
MMU ставился между процессором и памятью и обычные сегменты Z8010 становились «логическим адресным пространством», а аппаратный маппинг MMU уже превращал их в физические. Как я понял возможен даже трюк с шарингом одного сегмента в физической памяти между несколькими логическими сегментами разных процессов чтобы, допустим, на коде тех же DLL-ек экономить.
Забавно, что в один MMU вмещались дескрипторы только 64 сегментов, поэтому с одним MMU Z8000 мог обрабатывать в защите только 4Мб из 8 возможных.
Но достаточно было поставить еще один MMU в пару к другому — и возможно было обрабатывать аппаратной защитой все 128 возможных сегментов.
Но хуже того — можно было впаять и 4 MMU и сделать 16Мб физической памяти — и тогда с помощью MMU можно было разбрасывать задачи по 24-битному физическому адресному пространству хотя логические адреса конкретной задачи оставались 23-битными.
В общем Z8000 был реально не промах и наверное даже жаль, что его звезда закатилась. :)
Очень странно, ибо это реальный пакет где просто распаковываешь IDE, запускаешь, делаешь New->Project->From template и имеешь рабочий код который прямо в IDE же можно отлаживать проходясь отладчиком по реальным строкам кода, а не каким то там символам из отладочного файла. И всё это в ассемблере ca65 и всё это прям без проблем (ну кроме некоторых).
Может годы назад проект был глючным и не катил, да и сейчас вот глюки есть, но альтернатив я на горизонтах просто не заметил.
Весело девки пляшут. Довольно быстро обнаружил, что редактор Nesicide не воспринимает русские буквы ни под каким соусом — заменяет их на знаки вопроса хоть при вводе с клавиатуры хоть при загрузке из файла. А как мне комментарии писать для туториалов на русском то?
Залез на гитхаб в проект и посмотрел там на код. Оказалось, что для редактора используется компонента QsciScintilla у которой в документации по методу setUtf8 чёрным по белому написано:
Sets the current text encoding. If cp is true then UTF8 is used, otherwise Latin1 is used.
И конечно же оказалось, что нигде в коде инициализации setUtf8( true ) не вызывается.
Вот так да, все годы существования Nesicide автор похоже и не подозревал, что редактор в нём ничего кроме латиницы с некоторой диакритикой не воспринимает принципиально! И никто не пожаловался! =8()
В общем я сказал и автор пообещал исправить в следующих релизах. :)
P.S.
Вот опять таки посмотрел статьи migera по ссылке из начала статьи — хорошая информация, но опять не про то как сесть и начать программировать конкретный код с конкретными эффектами конкретными инструментами, а больше справочная информация. Мне же хочется написать именно такую статью, что садишься за неё, а через несколько часов встаёшь уже с работающим кодом в работающей среде никуда больше не заглядывая особо.
Я из таких лоскутов это собирал что уже не вспомню, иногда просто форумы какие то. Если это точно неправда, то надо исправить и тут. Прошу проверить даже такой момент — есть ли точки входа где байты в оригинальном пзу ненулевые.
byte MIRRORING | (HAS_SRAM << 1) | ((MAPPER & $0F) << 4) | NAME
и тут линкер выдал ошибку «Attribute expected» которая даже не гуглится особо да и я не понимаю как линкер может такое вообще отрабатывать нормально. Если только весь байт сразу там и определить разве что, но есть ли там битовые операции?
Уже даже не пойму какая аудитория выйдет идеальной для этого всего, уже даже закрадывается что как тот PHP-шник который стал делать выступления на конференциях как он стал писать на NES за месяц. :D
А ведь если задуматься, то такой подход (как у SjAsm) ничем не мешает и созданию кросс-платформенных форматов — генерацию каких то заголовков и служебных вещей тоже легко положить на какие то макросы из SAVEBIN OUTPUT_FILE которые обязательно надо выполнить в оговоренном порядке. В общем то дело техники. И у меня возникает подозрение, что FASM так и работает, поэтому конкретные форматы конкретных PE и COFF там дело библиотечного уровня.
Теперь и с SjAsmPlus всё понятно, спасибо.
Не оно разве?
Ни видел упоминаний сегментов в нескольких серьёзных исходниках, например Mighty Final Fight (под SjAsmPlus), но я тут реально слабо понимаю в том как именно в таком коде метки привязываются к переключению страниц.
У меня сложилось ощущение что просто через пару еще других ключевых слов и, вероятно, привязанных к виртуальным образам самого спектрума. Вообще не уверен.
Но разбираясь с секциями MEMORY и SEGMENTS ini-файла линкера cc65 очень быстро и легко понял как оно должно ложится на мапперы и любую структуру памяти любой машины. Это действительно сложно на первый взгляд, но понять нетрудно как и все выгоды — на самом деле даже очень просто.
Т.е. как переключать банки памяти на CA65 я понял даже быстрее чем на SjAsmPlus несмотря на то что со вторым столкнулся намного раньше.
Забавно, ведь во втором это сделано явно проще и для конкретной платформы, но глядя в документацию я не смог понять как.
А в CC65 просто поглядел пару минут в файл .ini с описаниями кусков памяти и сегментов и понял всё очень быстро.
Дальше только уточнения насчёт align и т.п. из официальной документации. И сразу понял кристально как сюда встраивать переключения банков.
Действительно вроде и сложнее, но по факту много проще.
Вероятно действительно выходит ну очень подробно и с остановкой на каждом шаге. Надеюсь так и есть.
Когда я искал аналогичные материалы, то наткнулся на статью на хабре про Си в рамках cc65 тут habr.com/ru/post/348022/
А это перевод англоязычных статей от Nesdoug: nesdoug.com/ где если в архив заглянуть, то в первоварианте от 2018 года написано: nesdoug.files.wordpress.com/2018/07/introduction-e28093-nesdoug.pdf
Воу, круто.
Тут еще на выходных пообщался с Кристофером который делает IDE Nesicide — он сейчас оказывается учавствует в джеме (сделать игру на NES за 48 часов) где вот: globalgamejam.org/2020/games/super-city-mayor-3
xD И снова Shiru!
Ну прям респект как говорится.
Так что скоро наверное будет вторая часть. Правда таки разрываюсь на тему того не сделать ли сперва одну часть краткого введения в ассемблер и архитектуру MOS 6502 чтобы статья в целом лучше заходила тем кто в этом ни бум-бум изначально, а так, из Си пришёл. Хм…
Перехват прерываний на ZX Spectrum
и как бы её продолжение:
Многопоточность на ZX Spectrum
Я знаю что про перехват прерываний не писал только ленивый и возможно это уже просто моветон. Про многопоточность вроде меньше материалов — поэтому вопрос: стоит ли тащить что либо из этого сюда?
Эмм… интересно конечно же как? Маппер VRAM который возвращает на верхние линии адреса частично состояние нижних?
Как уже написано у Z8000 16 РОН и 8Мб сегментируемой памяти. При этом сегменты сделаны еще круче, чем в i8086 — вместо того чтобы держать отдельные сегментные регистры при сегментированной памяти адресация памяти через регистры автоматически подразумевает, что в чётных регистрах содержится номер сегмента (в верхнем байте), а в нечётных — смещение в сегменте. Т.е. косвенная адресация через регистры возможна с восемью 32-битными регистровыми парами 16-битных регистров R0-R15 которые уже называются RR0-RR14 (и могут быть только чётными номерами). Объединение в регистровые пары — характерная черта i8080/Z80 которая в Z8000 присутствует вплоть до 64-битных регистров RQ0,RQ4,RQ8,RQ12 видимо для операций длинного деления — разбираться не стал.
Дальше-больше. Если в i8086 подытожить все формы адресации, то их можно выразить самой комплексной формой:
Т.е. или bx или bp сложенный с или di или si плюс смещение, при этом любая часть могла быть выкинута из выражения.
А вот в Z8000 самая сложная адресация возможная в любой команде — это [Rx+offset], где Rx это любой из шестнадцати его регистров кроме R0. А самая сложная адресация [Rx+Ry] где так же Rx и Ry это любые регистры кроме R0 возможна только в операциях передачи данных. С одной стороны вроде и менее сложные адресации, но с другой стороны этого более чем достаточно для эффективной адресации стековых переменных, а с другой стороны нет ограничений никаких какие именно регистры могут выступать в роли индексных, так что всё это немного похоже на современные RISC по возможностям.
Еще на англовики можно прочитать, что Z8000 неоднократно использовался в машинах на базе Unix. Чем же он был для них так привлекателен по сравнению с i8086? А вот чем:
Дело в том, что у Z8000 был дополнительный чип в _возможной_ обвязке — Z8010 именуемый еще Memory Managment Unit или MMU.
Так как сам Z8000 (точнее первая модель — Z8001) уже имел сегментированную память на 23 бита (8Мб) состоящую из 128 сегментов по 64Кб, то наличие MMU вроде бы смущает — казалось бы зачем?
Так вот как раз это самое — обеспечивать аппаратную защиту памяти read-only, code-only, data-only и system сегментов.
MMU ставился между процессором и памятью и обычные сегменты Z8010 становились «логическим адресным пространством», а аппаратный маппинг MMU уже превращал их в физические. Как я понял возможен даже трюк с шарингом одного сегмента в физической памяти между несколькими логическими сегментами разных процессов чтобы, допустим, на коде тех же DLL-ек экономить.
Забавно, что в один MMU вмещались дескрипторы только 64 сегментов, поэтому с одним MMU Z8000 мог обрабатывать в защите только 4Мб из 8 возможных.
Но достаточно было поставить еще один MMU в пару к другому — и возможно было обрабатывать аппаратной защитой все 128 возможных сегментов.
Но хуже того — можно было впаять и 4 MMU и сделать 16Мб физической памяти — и тогда с помощью MMU можно было разбрасывать задачи по 24-битному физическому адресному пространству хотя логические адреса конкретной задачи оставались 23-битными.
В общем Z8000 был реально не промах и наверное даже жаль, что его звезда закатилась. :)
Может годы назад проект был глючным и не катил, да и сейчас вот глюки есть, но альтернатив я на горизонтах просто не заметил.
Залез на гитхаб в проект и посмотрел там на код. Оказалось, что для редактора используется компонента QsciScintilla у которой в документации по методу setUtf8 чёрным по белому написано:
И конечно же оказалось, что нигде в коде инициализации setUtf8( true ) не вызывается.
Вот так да, все годы существования Nesicide автор похоже и не подозревал, что редактор в нём ничего кроме латиницы с некоторой диакритикой не воспринимает принципиально! И никто не пожаловался! =8()
В общем я сказал и автор пообещал исправить в следующих релизах. :)
Вот опять таки посмотрел статьи migera по ссылке из начала статьи — хорошая информация, но опять не про то как сесть и начать программировать конкретный код с конкретными эффектами конкретными инструментами, а больше справочная информация. Мне же хочется написать именно такую статью, что садишься за неё, а через несколько часов встаёшь уже с работающим кодом в работающей среде никуда больше не заглядывая особо.