Из журнала Adventurer#13, Ярославская область, г.Рыбинск, ?  PSB/Halloween Прямое программирование General Sound. Всем привет! Решил я написать немно- го о кульной спектрумовской звуковой кар- точке, а также о ее программировании. Но только не думайте, что это очередная ста- тья о том, как играть MOD'ы и эффекты или озвучивать игры... Подобной литературы в Спектрумовской прессе не было (или я про- сто не видел :-! ). Начну с самых истоков. Году в 1997 я приобрел GS (кстати, спасибо за это Hell- Raiser/Halloween), и как, наверное, мно- гие, стал учиться его программить, играл те же MOD и FX, озвучивал геймы, написал свой player MOD (который понимал и TR-DOS и MS-DOS ). Но все это потихоньку подна- доело, ведь разнообразие-то небольшое. А ведь когда GS еще только рекламировали, писали, что это - еще один комп, со своим процом, памятью и частотой раза в 3 выше. Так оно и есть. А главное, проц-то - ува- жаемый всеми нами Z80!!! Многие, наверное, до сих пор думают, как можно программировать GS напрямую, если в описании нет таких команд? Где лю- ди берут информацию по недокументирован- ным командам и вообще, как устроен GS? Думаете есть какие-нить доки по этому по- воду? Сейчас - ДА! Сейчас уже даже схему GS по кусочкам разобрали, а раньше всего этого не было, но узнать все равно было можно и даже просто. Опишу как это сделал я (по-любому, кому-то это будет интерес- но). В старые добрые времена была тради- ция (можно и так сказать) драть отовсюду AY'шные музоны. Ну меня и прибило дернуть MOD из Target Renegade и Xecutor. Дерну- ть-то дернул, а вот в Xecutor в загрузчи- ке нашел тест GS. Он помимо стандартной информации еще выдавал и копирайты из прошивки. Рассмотрев повнимательней про- гу, которая все это достает из карточки (там несколько разных кусочков достава- лось), прикинул, где может задаваться ад- рес, из которого это все берется... ну взял и набрал похожую прогу в MASM, толь- ко она у меня тянула с адреса #0000 пер- вые 32кб. А потом, посмотрев через STS (Z80 ведь!), что получилось, увидел прог- рамму! Все правильно, это было ПЗУ. Ну а дальше понятно: стал потихоньку разби- рать, что и как, нашел главный цикл, ко- торый обрабатывает поступающие команды, посмотрел, как работают документированные команды, из них узнал о некоторых портах, как выводится звук и так далее. Несколько экспериментов, и я знал уже практически все, задолго до появления подобной инфор- мации. И это все благодаря 3-м(!!!) кома- ндам из загрузчика Xecutor ! Хорошо, предположим, знаем мы Gene- ral Sound "изнутри", а что нам это дает? Да очень многое, точнее - использование платы нестандартным образом. Это может быть все, что угодно, начиная от дописы- вания процедур, которых не хватает в ста- ндартной прошивке, до своих программ, ко- торые, может быть, и к музыке-то не отно- сятся. Вот так. А сейчас, собственно, о программировании. Интерфейс со стороны Speccy 1. Command register (#BB=187, запись). 2. Status register (#BB=187, чтение). Биты: 7 - Data bit* 0 - Command bit* 3. Data register (#B3=179, запись). 4. Output register (#B3=179, чтение). *) Вообще, не помню точно, как описыва- лись значения этих битов после определен- ных операций в стандартной инструкции (типа, когда в какой-то порт что-то пишем или из него читаем, биты как-то меняют- ся), но могу сказать проще - БИТ УСТАНОВ- ЛЕН В 1, ЕСЛИ ОН _НЕ_ОБСЛУЖЕН_. То есть, если мы что-нить кинем в Data register, то Data bit будет в 1, пока GS из него не прочитает. То же самое будет и внутри GS - пока ZX не прочтет порт, в Data bit бу- дет 1. Особенности описания команд SC @$%&%$ - Послать код команды @$%&%$ в регистр команд WC - Ожидание принятия/выполнения команды (сброса Command bit) SD @$%&%$ - Послать данные @$%&%$ в регистр данных WD - Ожидание принятия данных (сброса Data bit) GD @$%&%$ - Принять данные @$%&%$ из регистра данных WN - Ожидание новых данных от GS (установки Data bit) Команды #18 - LD DE,nnnn SD nnnn_LOW SC #18:WC SD nnnn_HIGH Заносит в рег. пару DE значение nnnn (внутри GS!). nnnn_LOW и nnnn_HIGH - ста- рший и младший байты значения nnnn. #1A - Get data from (DE) SC #1A:WC GD Value Читает байт из ячейки, адресуемой DE. #1B - INC DE SC #1B:WC Увеличивает значение DE на 1. Таким образом, уже используя только эти 3 команды, вы можете прочитать память GS и узнать все, что надо: LD HL,#0000; адрес в GS, откуда хотим что-то прочитать LD A,L:CALL SD; делаем LD DE,nnnn в GS LD A,#18:CALL SC LD A,H:CALL SD LD HL,#8000; адрес в ZX, куда все будем складывать LD DE,#8000; длина блока LOOP LD A,#1A:CALL SC; берем значение из GS IN A,(#B3) LD (HL),A LD A,#1B:CALL SC; увеличиваем адрес в GS INC HL:DEC DE LD A,D:OR E:JR NZ,LOOP RET SC OUT (#BB),A WC IN A,(#BB):RRCA:JR C,WC RET SD OUT (#B3),A:RET WD IN A,(#BB):RLCA:JR C,WD RET WN IN A,(#BB):RLCA:JR NZ,WN RET Но есть и ряд других полезных (прос- то необходимых) команд. #14 - Put datablock to GS SD LENG_LOW SC #14:WC SD LENG_HIGH:WD SD GS_ADR_LOW:WD SD GS_ADR_HIGH:WD SD DataByte:WD ; столько раз подряд, сколько указано в LENG Засылает блок данных в GS по адресу GS_ADR и длиной LENG. Этой командой очень удобно загружать свои программы в GS. #13 - Jump to ADR SD ADR_LOW SC #13:WC SD ADR_HIGH Переходит по адресу ADR в GS. Подхо- дит для запуска загруженной программы. #10 - OUT (PORT),A SD PORT SC #10:WC SD Value #11 - IN A,(PORT) SD PORT SC #11:WC GD Value Эти две команды позволяют прочитать или записать значение в порт/из порта GS. Есть еще куча всяких полезных и бес- полезных команд (аля всякие ковоксы и т.п.), но для прямого программинга они нам не потребуются. Интерфейс со стороны General Sound Внутренние порты: #00, запись - открыть нужную страничку памяти #01, чтение - прочитать содержимое регистра команд #02, чтение - прочитать содержимое регистра данных #03, запись - заслать данные для Speccy #04, чтение - прочитать содержимое регистра состояния #05, запись - сброс бита Command bit в регистре состояния #06, запись - громкость канала A #07, запись - громкость канала B #08, запись - громкость канала C #09, запись - громкость канала D Есть еще порты #0A и #0B, но они вообще нафиг не нужны и не используются (хотя, кто хочет, по схеме может посмот- реть, что они делают ;). А теперь подробнее... 1. OUT (#00),Page (открыть нужную страни- чку памяти) В General Sound расположение памяти такое: #0000-#3FFF - первые 16к ПЗУ #4000-#7FFF - 16к ОЗУ #8000-#FFFF - страницы ОЗУ Т.е. странички в GS по 32 килобайта и располагаются с адреса #8000. В нулевой странице лежит ПЗУ целиком (т.е. по адре- сам #8000-#BFFF тоже, что и с #0000-#3FFF, а с #C000 - продолжение). В первой странице вторые 16к - копия памяти с #4000-#7FFF, а первые 16к - обычные. Вторая, третья и т.д. страницы - обычные страницы, которые можно свободно исполь- зовать. В базовом варианте GS (128к) 3 странички: 2, 3 и 4, а у GS512 - 14 стра- ниц (не учитывая 1-ю и 0-ю). Короче, чем больше памяти, тем больше страниц. В GS_ROM есть программа тестирования памяти (после reset), так вот там вроде задано аж 64 страницы... И еще по поводу памяти. На 512-ти килобайтной версии есть такой аппаратный "глюк": при включении одной из страниц, включаются сразу две, т.е. 32к пропадают. Поэтому всего у GS доступной памяти 51232=480к! 2. IN A,(#01) (прочитать содержимое реги- стра команд) Т.е. прочитать, что было по- слано Спеком в регистр команд (#BB). 3. IN A,(#02) (прочитать содержимое реги- стра данных) Т.е. прочитать, что было по- слано Спеком в регистр данных (#B3). 4. OUT (#03),Data (заслать данные для Speccy) По сути, передать данные для Спе- ктрума, т.е. их можно будет прочитать на ZX из регистра данных (#B3). 5. IN A,(#04) (прочитать содержимое реги- стра состояния) Биты: 0 - Command bit 7 - Data bit Назначение их такое же, как и у Sta- tus register со стороны Speccy. Например, если Command bit равен 1, значит поступи- ла команда и надо бы ее выполнить. Примечание: при работе с регистром данных (чтение/запись), Data bit _автоматически_ изменяется, в отличие от Command bit! Ес- ли со Спектрума послали число в регистр команд, то Command bit, как и положено, установится в 1, но он не сбросится при чтении его GS'ом. 6. OUT (#05),A (сброс бита Command bit в регистре состояния) Обычно этим дается знать компу, что команда в GS выполнена (или принята к обслуживанию). Число засы- лаемое в порт может быть любым. Примерчик (алгоритмик) работы со всем этим: > Ждем пока Command bit не установится в 1. Этим мы дожидаемся когда со Спека пос- тупит команда. > Берем значение из регистра команд (но- мер команды). В зависимости от этого пры- гаем куда надо. >...прыгнули, допустим, сюда... Необходи- мо дать понять Спеку, что команда приня- та, чтобы он не повисал в ожидании. Т.е. делаем OUT (#05), а затем выполняем нуж- ную прогу. Либо можно сделать иначе, если что-то нужно выполнить, а затем передать результаты компу: сначала все выполняем, запихиваем данные с помощью OUT (#03), a затем делаем OUT (#05). На Speccy надо будет дать номер команды, дождаться ее выполнения и смело брать данные из Output register. Вообще, здесь необходимо дейст- вовать логично, и если вы сделаете OUT (#05) раньше, чем OUT (#03), то рискуете взять на Спектруме не те данные. Ну да мне кажется, что такие моменты и так дол- жны быть ясны... 7. OUT (#06),Volume (громкость канала A) OUT (#07),Volume (громкость канала B) OUT (#08),Volume (громкость канала C) OUT (#09),Volume (громкость канала D) Громкости задаются числом от 0 до 63(#3F). Вывод в ЦАП: Он сделан весьма оригинально: по чтению из памяти. Так, сначала надо запи- сать нужное число в ячейку, а затем про- читать его оттуда. Канал (точнее сказать ЦАП), зависит от адреса: #6000-#60FF - для канала A #6100-#61FF - для канала B #6200-#62FF - для канала C #6300-#63FF - для канала D #6400-#64FF - для канала A #6500-#65FF - для канала B #6600-#66FF - для канала C #6700-#67FF - для канала D И т.д. до #7FFF. Эта область памяти удобно может быть использована под буфер, т.е. вы сначала быстро кидаете в нее данные, а потом в нужное время воспроизводите. Прерывания: Частота сигнала INT - 37500Гц, т.е. прерывания приходят 37,5 тыс. раз в секу- нду. На них обычно и вешают проигрывалку музы, точнее, прогу, которая кидает дан- ные в ЦАП'ы. Прикинем, 12000000/37500=320 тактов на прерывание. Это не так много, поэтому надо рассчитывать, чтобы то, что висит на прерываниях, не было слишком долгим, ина- че основная прога будет сильно тормозить. Практика Здесь будут рассмотрены конкретные примеры по программированию GS на ASM'е. 1. Определение наличия GS. ;CY=1, если GS отсутствует GS_DET LD A,#7F:IN A,(#FE):CPL; самый удачный вариант - RRCA:RET C; принудительное отключение user'ом. LD A,#F3:OUT (#BB),A; restart GS LD B,200; время (ZX_INTs), в течение которого ждем ; ответа от GS - 4 сек. GS_DET1 EI:HALT:DI IN A,(#BB):RRCA; смотрим, взял ли GS команду IN A,(#7B):JR NC,GS_DET2 ;in a,(#7b) нужен для того, чтобы прога не вылетала на Penta- ;gon'ах со включенным ZX-LPRINT3, т.к. если GS нет,то включит- ;ся ПЗУ ZX-LPRINT3! DJNZ GS_DET1 SCF:RET; GS так и не ответил GS_DET2 XOR A:OUT (#BB),A; след. этап - команда Reset Flags. GS_DET3 EI:HALT:DI IN A,(#BB):CP #7E; проверяем, сбросились ли флаги... IN A,(#7B):RET Z DJNZ GS_DET3 SCF:RET; время вышло, а флаги не сбросились ;Хотя... если вдруг из (#BB) всегда читается 0 (платы нет), то ;тест ошибется... Тогда надо бы (а надо ли вообще?) добавить ;тестов, например, заставить GS вернуть заданное вами значение ;(команда #10, порт 3). 2. Загрузчик программ в GS. INITgs CALL DEINIT; restart GS, если еще не делали LD HL,SUBPRG; адрес нашего вспомогательного загрузчика LD BC,#0080; длина блока LD A,C:CALL SD LD A,#14:CALL SC; загрузить блок в GS LD A,B:CALL SD:CALL WD XOR A:CALL SD:CALL WD; адрес загрузки #4000 LD A,#40:CALL SD:CALL WD LOOPiGS LD A,(HL):CALL SD:CALL WD; засылаем блок INC HL:DEC BC LD A,B:OR C:JR NZ,LOOPiGS XOR A:CALL SD; запускаем программу с адреса #4000 LD A,#13:CALL SC LD A,#40:CALL SD ;А дальше работает наш загрузчик, который подготовит все для ;загрузки нашей программы. Зачем такой изврат? Сначала, когда ;работало внутреннее ПО карты, стек был в районе #4400, а с ;#4000 шли всякие системные переменные этого ПО. А наш ма- ;ленький загрузчик переставит стек и, если надо, включит нуж- ;ную нам страницу и т.п. LD HL,PROG; адрес и длина самой программы LD BC,EPRG-PROG_ LOOpiGS LD A,(HL):CALL SD:CALL WD; засылаем блок INC HL:DEC BC LD A,B:OR C:JR NZ,LOOpiGS RET ;А вот и сам загрузчик. Учитываем, что он должен работать по ;адресу #4000, поэтому либо ассемблируем его соответственно, ;либо не используем прямых адресаций (JP, CALL) SUBPRG DI LD SP,#407F LD HL,#4080:PUSH HL; адрес загрузки в GS LD C,2; порт LD DE,EPRG-PROG_ SUBPRG1 IN A,(4):BIT 7,A:JR Z,SUBPRG1; ждем прихода байта INI:DEC DE; кидаем его в память LD A,D:OR E:JR NZ,SUBPRG1 RET ;Далее запускается наша прога. Шаблон такой: PROG ORG #4080,$ PROG_ ..... ..... EPRG ;org #4080,$ - это синтаксис STORM'а, а как это делается в ;других ассемблерах, вам лучше знать. Смысл такой, что прог- ;рамма физически располагается следом за основной, а ассембли- ;рована под адрес #4080. 3. Поиск свободной памяти в GS. LD HL,#FFFF LD A,#0F; максимум 15 страниц... LPP0 OUT (0),A:LD (HL),A; в страницу записываем ее номер DEC A:CP 1:JR NZ,LPP0; все страницы кроме 1-й и 0-й ;Можно, конечно, и 1-ю посчитать, но работать с ней не очень ;удобно (аля 5-й банк в Speccy). INC A LD DE,PAGETAB; таблица номеров страниц LD IX,0; будем считать количество страниц LPP1 OUT (0),A CP (HL):JR NZ,LPP2 LD (DE),A; если номер страницы совпадает с числом, INC DE,HX; записанным в ней, то заносим ее в таблицу LPP2 INC A:BIT 6,A:JR Z,LPP1; 15-я - последняя ;Дальше, если надо, можем очистить страницы. Но желательно ;учесть, что страниц может быть много, и лучше бы (если память ;позволяет) очищать через PUSH. ..... ;Можно передать информацию о количестве страниц Спеку. LPP3_ LD A,HX:OUT (3),A OR A:JP Z,0; если страниц нет, то RESET IN A,(4):RLCA:JR C,$-3; ждем пока он их возьмет ..... PAGETAB DS 14 4. Основной цикл и процедуры. ;Вариантов построения программы несколько. Если предполагается ;наличие небольшого количества команд, то можно просто поль- ;зоваться условиями CP #XX:JR Z,NNNN. Если же команд много, то ;лучше составить табличку с адресами. GSMAIN1 IN A,(4):RRCA:JR NC,GSMAIN1; ждем команду IN A,(1); берем ее номер LD HL,GSMAIN1:PUSH HL; по RET вернемся в цикл OR A:JR Z,PROG0; команда 0 CP 1:JR Z,PROG1; команда 1 CP 2:JP Z,PROG2; команда 2 CP #F3:JP Z,0; стандартные RESET'ы CP #F4:JP Z,0 ;если команда отсутствует, то ничего не делаем, а просто дадим ;Спектруму знать, что команда принята, а то он повиснет в ;ожидании OUT (5),A RET ;Вариант программы, когда сначала сигналим Спектруму, что все ;ОК, а затем исполняем прогу. PROG0 OUT (5),A ..... RET ;Вариант программы, когда сначала что-то исполняем, получаем, ;затем посылаем результат Спектруму, а потом уже сигналим, что ;прога выполнилась. PROG1 ..... OUT (5),A RET ;А можно и так выпендриться. PROG2 OUT (5),A ..... IN A,(4):RRCA:JR NC,$-3; ждем прихода любой команды ..... OUT (5),A RET ;Т.е. здесь будет так: вы посылаете со Спектрума команду #2, ;GS говорит, что команда принята, что-то исполняет и ждет при- ;хода любой команды. Потом опять что-то исполняет, и говорит, ;что все готово. Получается некий триггер. 5. Загрузка/выгрузка блоков данных. а) Вариант 1 - коротко и ясно. ;Загрузка данных (GS side): LD HL,#8000; адрес в GS LD BC,#4000; длина блока WAITD IN A,(4):RLCA:JR NC,WAITD; ждем байт IN A,(2):LD (HL),A:INC HL; принимаем DEC C:JR NZ,WAITD; повторяем DEC B:JR NZ,WAITD ;Выгрузка (GS side): LD HL,#8000; адрес в GS LD BC,#4000; длина блока WAITD_1 LD A,(HL):OUT (3),A; высылаем байт WAITD_ IN A,(4):RLCA:JR C,WAITD_; ждем принятия INC HL DEC C:JR NZ,WAITD_1; повторяем DEC B:JR NZ,WAITD_1 ;Загрузка данных в GS (ZX side): LD HL,#8000 LD BC,0-#4000; (0 минус длина блока) GS1 LD A,(HL):OUT (#B3),A; кидаем IN A,(#BB):RLCA:JR C,$-3; ждем принятия INC C:JR NZ,GS1 INC B:JR NZ,GS1 ;Выгрузка данных из GS (ZX side): LD HL,#8000 LD BC,0-#4000 GS2 IN A,(#BB):RLCA:JR NC,$-3; ждем поступления IN A,(#B3):LD (HL),A; принимаем INC C:JR NZ,GS2 INC B:JR NZ,GS2 б) Вариант 2 - ускоренный за счет раскры- тия циклов, но при этом должна быть фик- сированная длина блока. ;Загрузка данных (GS side): LD HL,#8000; адрес в GS LD E,8; количество циклов LD C,2; порт LOAD_2 .0 IN A,(4):RLCA:JP NC,$-3:INI; повторяем 256 (0) раз DEC E:JP NZ,LOAD_2; 256*8=2048 байт примем ;Загрузка данных в GS (ZX side): LD HL,#8000 LD C,#B3; порт данных LD E,8; количество циклов LOAD_2_ .0 OUTI:IN A,(#BB):RLCA:JP C,$-3; повторяем 256 (0) раз DEC E:JP NZ,LOAD_2_; 2048 байт передадим ;Смысл понятен - раскрываем циклы, тем самым жрем память, но ;немного повышаем быстродействие. Остальные процедуры, надо ;будет, сами напишете... Кстати, цикл ожидания тоже можно не- ;много развернуть: ;было IN A,(#BB):RLCA:JP C,$-3; 11+4+10=25 тактов ;стало LOOOP IN A,(#BB):RLCA:JR NC,LOOOPE; 11+4+7=22 такта IN A,(#BB):RLCA:JR NC,LOOOPE IN A,(#BB):RLCA:JR C,LOOOP LOOOPE ;Т.к. jr при невыполнении условия длится 7 тактов, то мы эко- ;номим по 3 такта на цикл. Т.е. опрос происходит чаще. в) И еще один не менее интересный спо- соб: ;Загрузка данных (GS side): LD HL,#8000; адрес LD C,1; порт IN A,(4):RLCA:JP NC,$-3; \ INI; +повторяем нужное кол. раз IN A,(2):LD (HL),A:INC HL; / OUT (5),A;чтобы потом GS не подумал,что пришла команда ;Таким образом, прием идет через 2 порта. Т.е. через порт 1 ;(#BB на ZX) идет первый байт, а через порт 2 (#B3) - второй. ;Загрузка данных в GS (ZX side): LD HL,#8000; адрес LD C,#BB; порт OUTI; \ LD A,(HL):OUT (#B3),A:INC HL; +повт. нужное кол. раз IN A,(#BB):RLCA:JP C,$-3; / ;Воспользоваться данным методом для передачи данных из GS не- ;возможно, т.к. в этом направлении порт только один. 6. Вывод звука. а) Проигрывание сэмпла из памяти без пре- рываний. LD HL,#8000; адрес сэмпла в GS LD BC,#8000; длина сэмпла LD DE,#6000; адрес ячейки ЦАП (#61XX, #62XX, #63XX) LOOP LD A,(HL); берем байт LD (DE),A; заносим в память LD A,(DE); кидаем в ЦАП .....; некоторая задержка (EI:HALT или NOP'ы ?!?) INC HL:DEC BC LD A,B:OR C:JR NZ,LOOP б) Проигрывание сэмпла из памяти, с испо- льзованием прерываний. ;программа, висящая на прерываниях, может выглядеть так: ;DE=SMP_ADR INT PUSH HL,AF LD HL,#6000; адрес ЦАП'а LD A,(DE):INC DE; берем очередной байт сэмпла LD (HL),A:LD A,(HL):INC H; ЦАП A LD (HL),A:LD A,(HL):INC H; ЦАП B LD (HL),A:LD A,(HL):INC H; ЦАП C LD (HL),A:LD A,(HL); ЦАП D POP AF,HL EI:RET ;Естественно, что проигрывание не будет остановлено - нет та- ;кого условия, т.к. это только пример. Причем плохой. Прерыва- ;ние не должно быть слишком длинным, поэтому желательно убрать ;оттуда лишние команды, да и вообще, организовать работу по-д- ;ругому. Сделать область памяти #6000-#60FF и т.д. буфером, в ;который мы быстро напихаем данные, а потом они будут играться ;со своей скоростью. А когда закончатся, снова напихаем! ;HL'=#6000 INT EXX:EXA; EXA=EX AF,AF' LD A,(HL); кидаем в ЦАП INC L:JR Z,FILLBUF; если буфер кончился... EXA:EXX EI:RET FILLBUF EXX:EXA EI FB1 LD HL,#8000; адрес сэмпла LD BC,256; длина буфера LD DE,#6000; адрес буфера LDIR LD (FB1+1),HL; сохраняем след. адрес RET ;Несмотря на то, что FILLBUF вызывается из прерываний и раз;- ;решает их, ничего страшного не произойдет, если успеть все ;сделать, пока буфер не закончился опять. Также надо успеть ;закинуть первый байт до того, как придет прерывание, иначе ;проиграется не то и будет щелчок. Выходом из этого положения ;является создание 2-го буфера, таким образом, пока один иг- ;рается, заполняем второй. ;Кстати, частота дискретизации в этом случае будет 37500Гц, ;поэтому, если вам надо меньше, то придется изменить алгоритм ;поставить делители частоты (в FILLBUF), которые заставят один ;и тот же байт повторяться несколько раз. 7. Отладка. В связи с отсутствием отладчика под GS, написанные программы нелегко отлажи- вать. Но можно воспользоваться тем, что GS умеет воспроизводить звук, отследив тем самым этап, на котором прога глючит. Напишем пищалку: GSBEEP LD BC,#0010,DE,#6000 GSBEEP1 LD A,120:LD (DE),A,A,(DE) DJNZ $ LD A,128:LD (DE),A,A,(DE) DJNZ $ DEC C:JR NZ,GSBEEP1 ;сюда можно вставить паузу, на случай, если 2 бипа пойдут че- ;рез небольшое время RET Теперь можно навставлять в программу обращений к GSBEEP и слушать, сколько би- пов будет до сбоя... Найти ошибку в алгоритме можно с по- мощью STS. Просто нужно программу для GS поместить в память ZX-Spectrum (по тем же адресам) и пошагово исполнять, только ко- манды in a,(xx) пропускать, а в аккумуля- тор заносить нужные значения вручную (out (xx),a можно просто пропускать). Кстати, на данный момент уже появи- лась низкоуровневая эмуляция GS в Z80Stealth, там же есть и отладчик, так что можно пользоваться им, правда, вот, только не очень удобно... Ну вот пока и все. Надеюсь, я ясно все об'яснил и показал. Теперь любой че- ловек, знающий ассемблер Z80, может легко разобраться с прямым программированием GS. Кто знает, может скоро появится куча хороших программ под GS ? Никто не хочет сделать дебагер для Speccy с ядром в GS? ;-)))