19 октября 2016, 13:59 (2987 дней назад, №10316)Архитектура и программирование компьютера TI-99/4a
Компьютер
Texas Instruments TI-99/4a почти неизвестен за пределами США, однако он был там весьма популярен (выпущено более двух миллионов машин). Хотя этот компьютер создавался как домашний, существенной особенностью (во многом определившей его архитектуру, а затем и судьбу) было то, что за основу был взяли уже существовавший серьёзный мини-компьютер
TI-990, собранный на обычной ТТЛ логике. Фактически, микропроцессор TMS9900 в компьютере TI99/4A является реализацией TI-990, но в виде чипа. TI-990 был выпущен в 1975 году, а TMS9900 в 1976 году.
Таким образом, TI99/4a (в 1979 был выпущен чуть более простой TI-99/4, а в 1981 уже TI-99/4a) получил в наследство крайне странную, для домашних компьютеров, архитектуру. Во-первых, микропроцессор TMS9900 в нём 16-разрядный - с честной 16-разрядной шиной данных (это в конце 1970-х!). Во-вторых, на чипе нет регистров (кроме PC, флагов и указателя "регистров" WP). То, что можно назвать регистрами, находится в отдельной микросхеме 16-разрядного статического ОЗУ размером 256 байт и может адресоваться одновременно как память и (первые 16 слов) как регистры R0..R15. Называется это "scratchpad".
Аппаратного стека нет, вместо этого сохранение значений при вызове подпрограмм осуществляется изменением указателя начала регистров WP в этом самом ОЗУ (напоминает регистровые окна в Sparc'ах). В предке (TI-990) это также использовалось для переключения контекста при реализации многозадачности.
Хотя тактовая частота TMS9900 - 3 МГц, инструкции занимают довольно много тактов - не менее 8. При этом реализовано даже умножение и деление (124 такта).
Всё, кроме процессора, увы, более аскетично. Кроме упомянутого крохотного статического ОЗУ есть ещё видеопамять - 16кб медленной 8-разрядной DRAM, доступ к которой осуществляется через видеоконтроллер. Другими словами, без дополнительной периферии TI99/4A представляет собой 16-разрядный компьютер с ОЗУ 256 байт, встроенными в ПЗУ BASIC и программами на картриджах (в них до 8кб честно адресуемого ПЗУ или более, с разными хитростями ).
В голый компьютер даже с кассеты загрузить нормальную программу невозможно, т.к. нет для этого ОЗУ (256 байт не в счёт, а остальное - видеопамять).
Поэтому минимально-адекватная конфигурация компьютера, это PEB (Peripheral Expansion Box) - специальная большая корзина для плат расширения, дисковод и расширение памяти 32kb. Либо NanoPEB - современное устройство, включающее всё перечисленное.
К PEB/NanoPEB необходим ещё картридж Editor/Assembler (без которого можно загружать и запускать лишь программы на Бейсике и который заодно содержит упомянутые дополнительные 32kb ОЗУ).
Видеоконтроллер TMS9918 (также используемый в MSX и ColecoVision) поддерживает графический (256x192, 15 цветов) и текстовый режимы, позволяет менять знакогенератор, может выводить одновременно 32 аппаратных спрайта 8x8 и 16x16 (правда, в одной строке может находиться одновременно не более четырёх штук).
Звук выводится через TMS9919 (его аналог, SN76489, использовался в IBM PCjr, BBC Micro, ColecoVision, некоторых консолях Sega):
4 канала (три генератора прямоугольников и один - шума), 16 уровней громкости.
Считается, что такая неравномерная в плане разрядности и местами несуразная архитектура получилась из-за того, что не была во-время готова 8-разрядная версия процессора TMS9900 (TMS9985).
Что касается программного обеспечения, то TI99/4A предназначался для целей образования (фактически, в противовес Atari и Commodore). В совокупности с описанными выше странностями архитектуры это привело к тому, что хороших игр на нём весьма немного (если вообще есть). Тот факт, что компьютер был распространён исключительно в США, добавил к этому ещё и отсутствие демосцены (моя интро 99tro была, кажется, второй на этой платформе).
СОВРЕМЕННОЕ СОСТОЯНИЕ ДЕЛ
Как уже было упомянуто, с голым TI99/4a мало что можно сделать, разве что с картриджей что-то запускать (если они есть). Необходимая для нормальной работы корзина расширения PEB стоит весьма недёшево и занимает много места. Поскольку исторически получилось так, что вся серьёзная разработка и вообще какие-либо действия с TI были завязаны на тот самый PEB, современные фанаты машинки пошли по пути его эмуляции (с эмулятором ROM картриджей даже никто, похоже, и не заморачивался).
Современным заменителем PEB является
NanoPEB. При некотором терпении его можно найти на ebay или у отдельных продавцов за 50...80e.
Это устройство втыкается в правый порт компьютера (туда же, куда и синтезатор речи. В т.ч. можно воткнуть его через синтезатор) и эмулирует 32кб дополнительного ОЗУ, три дисковода (на CompactFlash карте) и порт RS-232.
Существует также устройство CF7+, в котором всё тоже самое, но вместо RS-232 имеется параллельный порт Centronics.
Само по себе наличие NanoPEB позволяет загружать лишь программы на Бейсике (а из стандартного TI Бейсика невозможно запустить программу в машинных кодах, если не считать совсем уж грязных и нетривиальных хаков).
Поэтому совершенно необходим ещё картридж Editor/Assembler (EA), втыкаемый в верхний порт. С ним уже можно загружать и запускать нормальные программы. Картридж также ищется на Ebay. Вроде бы, вместо него можно использовать картридж Extended Basic (XB).
Для переноса программ на CompactFlash карту необходимо использовать специальные утилиты. Самая популярная - ti99dir (под Windows).
Стоит упомянуть проект
F18A - реализацию видеоконтроллера TMS9918A на FPGA, с рядом дополнений. Т.е. видеочип в обычном TI-99/4a заменяется на новый, при этом весь старый софт идёт как прежде, но появляется возможность выводить изображение на VGA монитор, устанавливать ранее недоступные видеорежимы и т.п.
РАЗРАБОТКА
Помимо программ на Basic, о которых мы говорить не будем, существует два основных формата, в которых может быть представлена программа в машинных кодах - для загрузки с диска через Editor/Assembler и для картриджа.
Файлы, загружаемые через EA, называются EA3 (потому, что в Editor/Assembler для их загрузки надо выбрать третий пункт меню - "LOAD AND RUN"). Они могут запускаться в любой области памяти (т.е. EA при загрузки выполняет линковку). Для эмулятора такие файлы хранятся на образе дисков (.DSK) и имеют формат Dis/Fix 80.
Так же, реже, могут попадаться memory image файлы называемые EA5 (в EA загружаются выбором пятого пункта меню - "RUN PROGRAM FILE"). Это уже слинкованные файлы, привязанные к адресам памяти. В эмуляторе также хранятся в образах дисков как "Program".
Загрузка с диска программы в кодах в обязательном порядке требует катриджа Editor/Assembler (или аналога).
Программы для картриджей жёстко привязаны к адресам и представляют собой просто бинарник, который прошивается в ПЗУ (для эмуляторов это формат .RPK, реже - .BIN).
Существует два существенно различных подхода к написанию программ на ассемблере. Первый - использование т.н. языка GPL. Это что-то среднее между Extended Basic (XB) и Assembler, как по производительности, так и по возможностям. Т.е. для всего интенсивно используются подпрограммы существующие в ПЗУ и, что важно, должны быть включены прерывания.
Второй вариант - ассемблер, о котором и пойдёт речь.
Основная необычность TI-99/4A, по сути, в процессоре TMS9900. От большинства процессоров, которые использовались в домашних компьютерах того времени, его отличает следующее:
Физически в процессоре всего три регистра - ST (флаги), PC (указатель команды) и WP (указатель рабочей области). Регистры R0-R15 находятся в статическом ОЗУ снаружи чипа. Адрес, с которого они начинаются, хранится в регистре WP и может быть изменён. Обычно его устанавливают равным >8300 (знак ">" означает шестнадцатиричную систему, знак ":" - двоичную).
С >8300 по >83FF находится 256 байт статического 16-разрядного ОЗУ, называемого "scratchpad".
Работа с ним примерно в 2 раза быстрее, чем с остальным динамическим 8-разрядным (стандартным расширением 32kb).
При этом имеется много режимов адресации и одной командой можно, к примеру, перенести значение из ячейки памяти в другую, заодно его изменив.
В процессоре нет стека - предполагается использование отображения регистров на любую область памяти командой LWPI addr для сохранения и восстановления контекста.
Вызов подпрограмм осуществляется командой BL @ADDR , которая заносит в регистр R11 следующий после неё адрес. Соответственно, для возврата используется B *R11 (она же RT). Есть также BLWP, сохраняющая кроме адреса возврата ещё и workspace (но при этом медленная).
Обычно первым делом выключают все прерывания и устанавливают начало регистров на начало статического ОЗУ. Поэтому заготовка программы выглядит так:
DEF START
WRKSP EQU >8300
START LIMI 0
LWPI WRKSP
...
* SOME CODE HERE
END START
Из-за такой регистровой архитектуры появляются особенности программирования. А именно:
1.К регистрам можно обращаться не только как к R1, R2 и т.д., но (поскольку они находятся в адресном пространстве) и как к ячейкам памяти. Это, в частности, позволяет работать отдельно с младшими и старшими байтами регистра, которые для этого заранее задают константами:
ws0 equ >8300 ; Workspace 0
; Register direct low-byte access
R0L equ ws0+1 ; Workspace 0 R0 low byte
R1L equ ws0+3 ; Workspace 0 R1 low byte
R2L equ ws0+5 ; Workspace 0 R2 low byte
Однако понятно, что далеко не всегда в команде можно просто заменить регистр на память (для данной команды может просто не оказаться нужного режима адресации).
С другой стороны, это всё равно бывает необходимо, т.к. команд, работающих с половинками регистров - немного (MOVB, SWPB, AB, SB, некоторая логика).
Конечно, независимо от текущего установленного workspace всегда есть доступ к любому регистру в любом workspace (по соответствующему ему адресу в памяти).
2.Поскольку стека нет (иногда, впрочем, его имитируют программно), сохранение и восстановление регистров осуществляется переключением workspace. Если этим пользоваться интенсивно, имеющиеся 256 байт быстрого SRAM закончатся быстро - их не хватает всегда.
Чтобы показать, как выглядит код для TMS9900, небольшой пример - Hello World:
DEF START
WRKSP EQU >8300
VDPWD EQU >8C00 * VDP RAM write data
VDPWA EQU >8C02 * VDP RAM read/write address
START LIMI 0 * disable interrupts
LWPI WRKSP * set default workspace
* set VDP RAM start address (low and high byte)
LI R0,>0000
ORI R0,>4000
SWPB R0
MOVB R0,@VDPWA
SWPB R0
MOVB R0,@VDPWA
LI R1,HELLOWORLD * ascii string address
LI R2,12 * total chars
NEXTCHAR
MOVB *R1+,@VDPWD * put next char on screen
DEC R2
JNE NEXTCHAR
LOOPBACK
JMP LOOPBACK * stop and do nothing
HELLOWORLD
TEXT 'HELLO WORLD!' * string data
BYTE 0
END START
Это современный (не совсем традиционный) ассемблер TMS9900. В первоначальном, помимо отсутствия двоеточий после меток, регистры записывались не как R с цифрой, а как просто цифра. Т.е. LI 4,5 и MOV 7,8 в старом ассемблере это LI R4,5 и MOV R7,R8 в новом.
Как легко видеть, это не сильно добавляло читаемости, так что теперь пишут R (хотя, даже современные ассемблеры понимают старый вариант записи). Некоторые люди, впрочем, мешают оба синтаксиса. В результате в одном и том же исходнике можно встретить рядом мнемоники SLA R5,0 (сдвиг R5 влево на количество разрядов из регистра R0) и SLA R5,1 (сдвиг R5 влево на _один_ разряд).
Некоторый (медицинский :) интерес представляет команда X, выполняющая инструкцию, код которой передаётся ей в качестве параметра.
; Здесь на самом деле выполняется инструкция B *R11
LI R9,>045B ; код инструкции B *R11
X R9
; А здесь на самом деле выполняется инструкция LI R1,>1234
LI R0,>0201 ; код инструкции LI R1,xxx
X R0
DATA >1234 ; это операнд xxx (>1234) для этой инструкции
Считается, что X можно использовать в целях отладки, а также для переходов по табличкам. Ну и в любых ситуациях, где нужен самомодифицирующийся код.
КАРТА ПАМЯТИ
>0000 -----------
Console ROM
>2000 -----------
Low memory expansion
>4000 -----------
Peripheral cards ROM
>6000 -----------
Cartridge ROM/RAM
>8000 -----------
Scratchpad RAM memory mapped devices
(С >8300 по >83FF находится статическое ОЗУ объемом 256 байт.
Регистры процессора чаще всего отображают начиная с >8300. С >8400 по >9C02 адреса для доступа к видео, звуку, синтезатору, GROM )
>A000 -----------
High memory expansion
(стандартное расширение ОЗУ 32kб. С >A000 обычно начинается типичная загружаемая с диска программа)
...
>FFFF
Что касается ПЗУ, в TI-99/4a используются так называемые GROM. Они не отображаются целиком в адресное пространство процессора, а доступ к данным осуществляется последовательно - по специальному адресу заносится начальный адрес данных в GROM и затем по другому специальному адресу оттуда читаются данные. При этом текущий адрес данных GROM увеличивается автоматически, при каждом чтении.
Печать строки символов из GROM
li r0, >0874 ; starting GROM address of small character set GROMSC
movb r0,@GROMWA ; set GROM read pointer to address 06B0 (high part)
nop ; wait a beat (not sure if necessary)
movb @WR0LB,@GROMWA ; set GROM read pointer to address 06B0 (low part)
li r0,>0000+(8*32*8) ; VDP Pattern Table start address
movb @R0L,@vdpwa ; Send low byte of VDP RAM write address
ori r0,>4000 ; Set read/write bits 14 and 15 to write (01)
movb r0,@vdpwa ; Send high byte of VDP RAM write address
li r1,GROMRD
li r2,32
nextchar:
movb *r1,@vdpwd
movb *r1,@vdpwd
movb *r1,@vdpwd
movb *r1,@vdpwd
movb *r1,@vdpwd
movb *r1,@vdpwd
movb *r1,@vdpwd
; movb *r1,@vdpwd
clr @vdpwd ; skip one byte
dec r2
jne nextchar
ГРАФИКА
Для вывода графики и текста, в TI-99/4a установлен видеочип VDP TMS9918a, который используется также в
MSX1 и
ColecoVision.
VDP позволяет выводить графику с разрешением 256x192 в 15 не слишком удачных цветах (+1 прозрачный, через который просвечивает фон) и одноцветные спрайты - 32 штуки одним размером 8x8 либо 16x16 с существенным ограничением: не более 4-х спрайтов на одной строке.
В VDP 16kb DRAM и она распределяется между графикой (текстом), цветами и спрайтами.
Видеопамять не отображается в адресное пространство процессора. Для записи в неё нужно сначала занести по определённому адресу начальный адрес в видеопамяти, по которому будет осуществляться запись, а затем писать подряд нужные значения по другому адресу (одному). При этом каждое следующее значение окажется в видеопамяти по увеличивающемуся на единицу адресу.
В зависимости от видеорежима и распределения памяти можно выбрать оптимальный - с точки зрения удобства адресации, используемой памяти и количества цветов (в пиксельном блоке) режим. По своей сути все режимы - текстовые. В том смысле, что во всех случаях есть понятие "символы" (characters), которые можно помещать в разные места экрана и которые можно менять. Но, поскольку можно перепрограммировать символы так, чтобы их вид соответствовал их коду, то по факту режим уже оказывается "графическим". Впрочем, это типично для многих видеоконтроллеров 1980-х. Даже в сравнительно современном VGA была такая возможность (автор её использовал для реализации быстрого режима 640x400 с отдельной памятью для цветов).
Чаще всего в TI используется Graphic Mode I (именно в этом режиме оказывается компьютер при включении и инициализации). В нём можно задать два цвета (фона и изображения) на блок 8x8. Всего блоков 32x24. Единственное преимущество этого режима - сравнительно высокая скорость изменения цветов.
Есть также Multicolor mode - графический режим с разрешением 64x48, где на каждый крупный пиксел задаётся цвет (но при таком разрешении разве что плазму какую-нибудь рисовать или ANSI art ) и пара чисто текстовых режимов 40x24 символа - цветной и монохромный.
Наиболее мощным и интересным режимом в VDP представляется так называемый Graphic Mode II (он же "bitmap mode"). Этот режим позволяет использовать два цвета (фона и изображения) внутри пиксельного блока 8x1. Иными словами - 2 цвета на каждые 8 горизонтальных пикселов. К сожалению, порядок блоков (что пикселов, что их цветов) крайне неудобен для всего, кроме вывода символов - байты идут сверху вниз 0-8, затем следующий правее опять сверху вниз 8-15 и т.д. до правого края. Далее всё повторяется вновь слева направо.
При установке режима также задаются адреса для атрибутов (Color Table), битмапа (Pattern), порядка символов, образующих блоки для битмапа (Name table), данных для спрайтов (Sprites data) и атрибутов спрайтов (Sprites Attributes). Адреса можно задать не любые - там просто несколько вариантов, которые следует выбрать так, чтобы в итоге без нахлёстов рационально использовались все 16384 байта видеопамяти. К примеру, для своей intro "99tro" я распределил память так (Graphics Mode II):
>0000 - >1800 Pattern table - 6144 bytes (>1800)
>1800 - >2000 Sprite patterns - 64 sprites x 32 bytes = 2048 bytes (>800)
>2000 - >3800 Color table - 6144 bytes (>1800)
>3800 - >3b00 Name table - 768 bytes (>300)
>3b00 - >3b80 Sprite attrs =32 sprites x 4 bytes = 128 bytes (>80)
Спрайты могут быть двух размеров - 8x8 или 16x16 (переключаются сразу все спрайты) и дополнительно можно растянуть их (тоже сразу все) в два раза.
Всего спрайтов отображается одновременно на экране 32 (но не более 4 в одной строке). При этом 2кб памяти для Sprite patterns хватает на два набора по 32 спрайта 16x16.
Индивидуально для каждого спрайта можно установить следующие параметры (через область Sprite attrubutes, для каждого байта в серии):
0 - вертикальная координата (-32 - 191)
1 - горизонтальная координата (0-255). 255 - спрайт за правым краем экрана.
2 - указатель на место откуда брать паттерн (в Sprites patterns)
3 - биты 4-7 определяют цвет, бит 0 - early clock (сдвигает спрайт по горизонтали на 32 пиксела влево)
Отсутствие прямой адресации видеопамяти в комбинации с неудачной её организацией и отсутствием в процессоре команд пересылки более чем байта за раз - не позволяет её быстро обновлять. К примеру, реализация горизонтального гладкого скроллинга области 256x8 пикселов при параллельном проигрывании VGM музыки почти полностью забирает время обратного хода луча по кадру. Т.е. больше времени почти ни на что не остаётся.
В играх (современных) эта проблема решается довольно хитрыми способами, когда при скроллинге обновляется не вся скроллируемая область, а только требующая изменений. Соответственно, содержимое экрана продумывается таким образом, чтобы этих изменений был минимум. Также обычно используется Graphics Mode I с более низким цветовым разрешением (два цвета на блок 8x8).
Как и во многих других системах, в TI-99/4a крайне желательно производить отрисовку изображения укладываясь в обратный ход луча по кадру (vertical retrace). Для этого VDP генерирует соответствующее прерывание - в начале рисования нижней части рамки.
Существуют следующие варианты действий:
1. Прерывания можно отключить полностью (инструкцией LIMI 0 + конкретно для VDP - установкой соответствующего бита в регистре 1). При этом всё будет побыстрее, но не получится отследить обратный ход луча никаким образом.
2.Прерывания включить полностью (LIMI 2 и в VDP). Тогда можно повесить собственный обработчик прерываний, который будет что-нибудь делать.
3.Включить прерывания в VDP, но отключить их инструкцией LIMI 0. В этом случае никакие обработчики вызываться не будут, но за обратным ходом луча можно следить вручную, проверяя бит в регистре состояния VDP (после чтения регистра этот бит автоматически сбрасывается. Если его не сбросить, то не сгенерится очередное прерывание).
vwait:
movb >8802,r12 ; read VDP status register
andi r12,>8000
jeq vwait ; Wait for vsync
Достаточно типична схема, когда прерывания от VDP включены, общие выключены (LIMI 0), но в основном цикле на короткий промежуток времени они включаются (LIMI 2), чтобы висящий на прерывании обработчик (например, играющий музыку) получал иногда управление.
Не все эмуляторы правильно имитируют это прерывание. Но, скажем, MESS делает это более-менее адекватно. На картинке можно видеть простой тест - цвет рамки меняется с некоторой задержкой относительно vertical retrace (на настоящем TI и на двух эмуляторах - MESS и js99er).
ЗВУК
1. Стандартный звук
Для получения музыки и звуковых эффектов в TI-99/4a установлен чип TMS9919 (аналоги: SN76489, SN76496). Он довольно прост: три независимых генератора тона и один - шума. 15 уровней громкости.
Поскольку это довольно популярный чип (использовался в некоторых консолях, в IBM PCjr/Tandy-1000 и пр.), информации по нему много.
Для TI-99/4a существует трэкер MOD2PSG (нормально запускается под Win7 64bit). В частности, он позволяет загружать файлы формата psgmod и экспортировать vgm и epsgmod.
Имеется два работающих плеера - EPSGMOD Player by Tursi ( требует данные в формате epsgmod, вешает свой обработчик на прерывание VDP) .
Подключается он следующим образом:
def start
start:
lwpi >8320
li r0,musicdata
bl @SGPLAY
again:
limi 2
limi 0
; .... your code (vsync etc..) ...
b @again
musicdata:
bcopy "music.epsgmod"
copy "test_music_playervbr.a99"
copy "test_music_player.a99"
end start
При таком подключении плеера, необходимо из его исходников убрать REF и END (поскольку они ориентированы на использование с традиционными ассемблером).
Обратите внимание, что в основном цикле на мгновение включаются прерывания (limi 2). Это необходимо для работы плеера, который работает по прерываниям VDP от обратного хода луча (происходящим каждую 1/60 секунды). При этом вызывается подпрограмма SGTICK плеера.
Соответственно важно, что для NTSC версии компьютера и музыка должна быть тоже NTSC (частота кадров должна совпадать).
Второй плеер (того же автора) проигрывает файлы формата VGM (включает также утилиту VGMComp для сжатия VGM). Этот свой обработчик не вешает, нужно вызывать вручную с частотой 60Гц..
Выглядит это так:
def start
vdpsta equ >8802 ; VDP RAM status
start:
limi 0
lwpi ws0 ; player expects our workspace to be >8300
clr r2
li r1,musicdata
bl @stinit
again:
clr r12 ; set CRU base (doesn't need to be in loop, but provides delay)
tb 2 ; check VDP interrupt input (Tests a CRU bit)
jeq loop ; if not set, skip calling music
movb @vdpsta,r12 ; read status register, which in turn clears the interrupt bit
bl @stplay ; call play function - it returns with the wrong workspace
* check for song end
movb @songwp+14,r3
jne notdone
clr r2 ; play again
li r1,musicdata
bl @stinit
notdone:
lwpi >8300 ; so restore the right one
; .... your code (vsync etc..) ...
b @again
musicdata:
bcopy "music.vgm"
copy "tiplayer.a99"
end start
На мой взгляд, у второго варианта плеера лучше качество (хотя, это может зависеть от конкретной музыки) и ручной вызов гибче, чем обработчик (который вы и сами можете навесить, если очень надо) - не нужно включать прерывания.
Также у него есть вариант плеера на 30гц. При этом вызывать его нужно тоже с частотой 60гц, но использовать он будет каждый второй вызов.
Соответственно, снижается загрузка процессора, но потенциально может упасть качество звучания музыки (зависит от используемых в ней эффектов).
По-видимому, в плеере (либо при экспорте в VGM) присутствует баг, создающий непонятные проблемы с зацикливанием музыки.
2. Синтезатор речи
Поскольку синтезатор речи является очень популярным периферийным устройством для TI-99/4a, довольно много программ его используют. Синтезатор присоединяется к порту в правой части компьютера (при наличии NanoPEB его можно воткнуть прямо в синтезатор). Внутри находится ПЗУ со данными для слов, LPC декодер (TMS5220) и ЦАП.
Имеется два режима работы. В первом компьютер указывает синтезатору, какие слова из ПЗУ следует произносить (слов там немного). Во втором (Speak-External) есть возможность передавать непосредственно сжатые LPC данные, которые синтезатор просто будет воспроизводить. Для упаковки данных используется древняя программа QBox Pro, которая запускается под WinXP (процесс показан
здесь).
Синтезатором можно управлять непосредственно из программы на ассемблере. Если же хочется просто поиграться, то необходим картридж - например, Speech Editor (позволяет проговаривать набранный текст, а также добавляет в стандартный Бейсик команды для воспроизведения слов). Картридж Terminal Emulator позволяет произносить отдельные фонемы и соединять их в произвольные слова.
В наиболее известных эмуляторах синтезатор поддерживается. Считается, что наилучшая поддержка (полностью эмулируется железка) реализована в MESS/MAME.
Ниже пример, который произносит слово "HELLO" беря LPC данные из ПЗУ синтезатора. Документация указывает, что проверка статуса невозможна, если код выполняется в ОЗУ на 8 битной шине (т.е. требуется чтобы код находился в 16 битном scratchpad), однако после проверки на эмуляторах и реальном компьютере я считаю, что это перестраховка (вероятно, как и в случае с VDP, все эти требования относятся к каким-то совсем ранним версиям железа). Получился вот такой короткий код:
; speaks 'HELLO' from speech synth ROM
def start
SPCHRD equ >9000 ; addr to read from synth
SPCHWT equ >9400 ; addr to write to synth (>10 - read, >4x - Load-Address, >50 - speak, >60 = speak-ext )
H50 BYTE >50 ; speak command
start:
limi 0
; loading speech address (5 nibbles of data required) using >4x Load-Address command
; >4x >4x >4x >4x >40 (end marker >0)
li r0,>351a ; address of 'HELLO' in synth ROM
li r2,4 ; 4 nibbles
loadlp:
src r0,4 ; start with least significant nibble
mov r0,r1
src r1,4
andi r1,>0f00 ; get only particular nibble
ori r1,>4000 ; put in >4x00 format
movb r1,@SPCHWT ; write nibble
dec r2
jne loadlp
li r1,>4000
movb r1,@SPCHWT ; write fifth nibble (end marker)
movb @H50,@SPCHWT ; execute speak command
loopback:
b @loopback
end start
Адреса всех слов из ПЗУ можно найти в книжке Editor Assembler Manual.
Если нужно воспроизвести произвольную речь преобразованную в LPC при помощи QBox Pro, то в самом простом случае код будет выглядеть так:
def SPEAK
SPEAK
li r2,1750 ; number of speech BYTEs to poke (see end of QBox data)
li r3,speech ; speech data address
limi 0 ; no interrupts
loop
movb *r3+,@>9400 ; poke a BYTE of speech data
dec r2
jeq quit ; repeat until end of data
jmp loop ; else go 'round again
quit
limi 2 ; interrupts allowed
rt
speech
; 10 Voiced 4 Unvoiced 8 kHz 5220
; [Coded LPC]
; .... данные из *.sfm полученном при помощи QBox Pro
; nb bytes: [1750]
end
Для QBox Pro нужно подготовить wav файл с речью. Лучше всего будет звучать чёткая мужская речь. Одна фраза будет занимать около килобайта.
Передать пение или ноты - не выйдет. После кодирования в LPC подобные звуки будут совершенно неузнаваемы.
Настройки и операции в QBox Pro:
byte
8khz
5220
Project/Add files
select file
Process/Medium bit rate /OK
Edit/Concatenations
Insert/Concatenation/Concat Name/ "TEST"
Insert/Phrase/OK
Format/LPC 10V, 4UV /OK
Project/Exit/Save
Результат: test.sfm
КРОСС-СРЕДСТВА
Чтобы разрабатывать и тестировать программы на PC, а затем загружать их на TI-99/4a, понадобится следующее:
Железо:
- PC/Win7 (64bit годится)
- TI-99/4a
- Картридж Editor/Assembler (или любой другой, позволяющий загружать и запускать программы в машинных кодах)
- NanoPEB или CF7+ (для эмуляции дисковода и загрузки программы с CompactFlash карты в TI-99/4a)
Софт:
- Один из многочисленных эмуляторов TI-99/4a - MESS/MAME или js99er (а лучше несколько).
- Пакет xdt99 , включающий ассемблер и утилиты для работы с файлами и образами дисков (в частности, xvm99 для записи образа диска на CompactFlash карту, в нужном для NanoPEB формате)
Организация процесса:
Программы для картриджа и для загрузки с диска - разные вещи. Не только по формату (RPK и DSK) но и в плане работы с памятью (второй вариант подрузамевает расширение памяти 32кб). Если есть цель в итоге запустить код на реальной машине, ориентироваться надо на дисковую версию. Её и рассмотрим.
Эмуляторы существенно отличаются друг от друга, лучший назвать очень сложно. Это не только вопрос качества эмуляции, но и удобства/простоты запуска готовой программы.
Наиболее известные: mess(mame), js99er, v9t9, classic99.
Все они не идеальны. Вполне можно ожидать 10% разницы в скорости между эмулятором и реальной железкой (конкретно я наблюдал, что mess и js99er чуть быстрее выполняют весь код чем реальный TI. В одном и том же NTSC)
При использовании в качестве ассемблера xdt99 процесс выглядит так:
xas99.py -R --jumpstart %1.a99 (получаем объектный файл)
Параметр -R позволяет нормально именовать регистры в исходном тексте (r0, r1, ...). В традиционном ассемблере TMS9900 регистры в командах записываются просто цифрами, что вносит дикую путаницу.
xdm99.py work.dsk -a %1.obj -n %1-O -f DIS/FIX80 (помещаем полученный объектный файл на образ диска)
Нужно учитывать, что объектный файл представляет собой текстовый файл с шестнадцатиричными кодами внутри. Хотя это не конечный бинарный файл, тем не менее из-за особенностей загрузки программ на TI-99/4a (через Editor/Assembler) его часто считают готовой программой. Тем более удобно, что он обычно не привязан к конкретной области памяти при загрузке.
Если требуется именно конечный бинарный файл, для xas99 следует добавить параметр "-i". Загружать его тогда нужно будет не через пункт 3, а через пункт 5 в Editor/Assembler. Если нужен конечный бинарный файл более 8к, без разбиения на отдельные части, вместо -i нужно использовать -b.
Дополнительно можно получить файл листинга .lst указав параметр "-L".
Несколько упрощает работу специальный образ картриджа для автоматической загрузки (см. "jumpstarting" в документации на xdt99). Образ позволяет в эмуляторе обойтись без Editor/Assembler, нажатия в нём кнопок и ввода имени файла. Готовый код загружается с диска автоматически при выборе картриджа. К примеру mess(mame) в этом случае запускается так:
mame64.exe ti99_4a -peb:slot8 tifdc -peb:slot2 speech -flop1 %1.dsk -cart jumpstart.rpk -window -ui_active -skip_gameinfo
Если нужен отладчик, можно ещё добавить -debug (вход в отладчик из эмулятора при условии, что была указана опция - клавишей ` )
Параметр "ti99_4a" подрузамевает NTSC версию компьютера (т.е. наиболее распространённую). Для PAL версии это будет ti99_4ae.
Отличие очень существенное, как минимум из-за разной частоты кадров -. в первом случае кадровая 60гц, во втором 50гц. Неправильный выбор может привести, например, к замедленному проигрыванию музыки (если она писалась под NTSC, а выбран PAL).
Если разрабатываемое приложение имеет существенный размер или что-то считает ощутимое время, можно ускорить эмуляцию параметром -speed 20
Для переноса записи программы на CompactFlash в формате NanoPEB можно использовать xvm99.py
\\.\PHYSICALDRIVE2 1 -w work.dsk
Убедитесь, что на вашем компьютере PHYSICALDRIVE2 соответствует именно устройству в которое вставлен CF (иначе можете испортить данные на своих дисках!). В Windows это можно посмотреть командой
wmic diskdrive list brief /format:list
Поскольку на одном CF может быть несколько образов для эмуляции нескольких дисководов, второй параметр соответствует дисководу - т.е. DSK1 для 1).
Предварительно CF рекомендуется отформатировать под FAT16, после чего обязательно на самом TI со вставленным NanoPEB из TI Basic набрать "call format(1)", где 1 - номер форматируемого виртуального дисковода.
Просмотр содержимого флэшки:
xvm99.py \\.\PHYSICALDRIVE2 1 -i
При загрузке программы на самом TI нужно вставить картридж с Editor/Assembler и NanoPEB. Включить NanoPEB, затем компьютер. В меню выбрать опцию "2" - "Editor/Assembler", в нём пункт 3 (либо 5), набрать DSK1.ИМЯ_ФАЙЛА и нажать ENTER.
Регистр в имени файла важен.
ПРИЛОЖЕНИЕ 1 - интро 99TRO
В процессе изучения TI-99/4a мной была написана
"99tro" (исходник) - небольшая invitation intro на Chaos Constructions'2016, представленная на Revision'2016. Дополнительно к её исходникам (код весьма ужасен - не пишите так!) здесь будут даны некоторые пояснения.
Основная идея в том, чтобы интро по возможности передавало стилистику старых cracktro для Commodore 64, естественно с поправкой на более скромные возможности видео и звукового чипов TI-99/4a.
Грубо говоря, хотелось что-то типа цветного логотипа, плавно скроллящейся текстовой строки, переливающихся цветных полосок и болтающейся надписи из спрайтов.
Вскоре стали ясны следующие существенные ограничения и недостатки компьютера:
- Аппаратного скроллинга нет, его можно сделать только сдвигами и перезаписью видеопамяти
- Обмен с видеопамятью (это касается и спрайтов и графики и текста) очень медленный и сравнительно высокая частота процессора не спасает (равно как и его 16-разрядность, т.к. обмен с VDP идёт по 8-разрядной шине).
- VDP имеет ряд недокументированных или невнятно документированных багов (что неудивительно, TMS9918 - один из первых специализированных видеочипов). Пару раз это приводило к тому, что от уже практически законченной части приходилось отказываться.
- Сильно ограниченный функционал спрайтов - только 4 в строчку (что автоматически означает, что ничего шириной в экран спрайтами не сделаешь), одноцветность, большое количество инструкций процессора для простой смены цвета или координаты спрайта.
- Проигрывание музыки (vgm) отнимает весьма существенное время процессора. Как заявлено в исходниках плеера от Tursi, это 2-20% для 30гц версии, взависимости от конкретной музыки. В моём конкретном случае получалось не меньше 10-15%.
- Беда с удобными отладчиками (на самом деле, та же картина была и с эмулятором Vectrex - в обоих случаях есть мощные отладчики, но неудобные настолько, что, по факту, их использование скорее замедляет работу, нежели ускоряет)
- Невозможность быстро проверить код на реальном железе. Для этого нужно всё выключить, вытащить флэшку, воткнуть в PC, скопировать на неё код, вытащить из PC и вставить в TI, включить эмулятор диска и сам TI, выбрать Editor/Assembler, выбрать пункт загрузки программы, набрать DSK1.имя_программы, нажать ENTER, дождаться загрузки (около минуты).
В итоге постепенно от прежних идей осталось только то, что позволяла реализовать железка (конечно, с учётом, что это была моя первая программа для этой платформы и этого процессора):
- Надпись/лого CHAOS CONSTRUCTIONS'2016 с головой робота
- Мигающие в такт музыке глаза и рот робота
- Появляющийся буква за буквой, вслед за пульсирующим курсором, текст в несколько строк
- Плавно скроллящаяся строка текста
- Фоновая музыка (vgm)
Чтобы хоть как-то использовать сильные стороны TI-99/4a, я выбрал режим с максимальным графическим и цветовым разрешением - Graphics mode II (Bitmap). Для игр его используют крайне редко, т.к. он требует всех 16кб видеопамяти и, соответственно, очень медленный с точки зрения обновления изображения.
Однако, в отличие от игр, в моём случае можно было пойти на хитрость (что вообще характерно для demo и intro) - цвета я динамически не обновлял вообще нигде. Всё, что касается цветов, было установлено единственный раз в начале, вне основного цикла.
В цикле же менялся только bitmap и переключались два спрайта.
Верхнее лого было нарисовано сначала в Photoshop, затем преобразовано в дамп видеопамяти для VDP при помощи утилиты Convert9918 и далее эти два этапа циклически повторялись - т.е. в исходную картинку вносились уточнения, чтобы после преобразования (в 15-ти довольно кривых цветах и цветовом разрешении 2 цвета на 8 горизонтальных пикселов) она выглядела максимально адекватно. Надо сказать, что искажения характерные для композитного NTSC сигнала сыграли, как часто в таких случаях бывает, на руку - на CRT мониторе картинка выглядит явно лучше, чем в эмуляторе (к тому же, теплее и ламповее).
Надо сказать, я впервые всерьёз осознал, насколько важен для успеха платформы правильный выбор 16 возможных цветов её (платформы) разработчиками. В случае с TI-99/4a эти цвета крайне неудачны (к тому же, их 15).
Кроме того, впервые пришлось столкнуться с проблемой кодирования цвета в NTSC видеосигнале. На фото видно, во что превращаются цвета для одиночных пикселов (в PAL это тоже вызывает проблемы, но несопоставимо меньшие).
Нижний плавный скроллинг был взят из единственного подобного примера (Matthew Hagerty) и модифицирован. Честно говоря. я бы за столь короткий срок вряд ли смог проникнуться ассемблером TMS9900 достаточно глубоко, чтобы повторить некоторые использованные там приёмы, без которых просто не хватило бы общей производительности для всей работы.
В скроллинге достаточно хитро сдвигаются влево части символов и результат копируется в VRAM. Всего скроллится область в 8 пикселов высотой.
Как я уже сказал выше, операции производятся исключительно над bitmap'ом - цветной фон в виде бело-синей полосы нарисован заранее и скроллингом не затрагивается.
Поскольку скроллинг - наиболее затратная операция в основном цикле, всё дальнейшее отталкивалось от него и от музыки (если перестаёшь укладываться в обратный ход луча, музыка начнёт тормозить).
К сожалению, по факту она всё равно немного тормозит т.к. оба эмулятора, на которых я отлаживал код, работали чуть быстрее, чем реальная железка. В чём именно заключается проблема - неясно.
В средней части экрана пишется текст за курсором. У меня было много идей как это красиво реализовать, но в итоге от всех пришлось отказаться из-за медленной видеопамяти и багов VDP. В результате, за каждую итерацию цикла рисуется одна буква (т.е. обновляется 8 байт видеопамяти). Для всех возможных восьми строк текста заранее установлен фон с плавным переходом между оттенками синего и серым.
Пульсирующий курсор представляет собой спрайт, для которого изображение берётся циклически из четырёх мест памяти - т.е. получается четыре кадра квадратика разного размера.
Что касается мигающих глаз и рта робота, то они реализованы тремя кадрами одного спрайта 16x16 (опять же - с целью выгадать в производительности). Тот или иной кадр выбирается взависимости от громкости одного из каналов звукового чипа (эти данные возвращает плеер).
Спрайты и шрифты редактировались в программе Magellan.
В коде используется три разных workspace для регистров процессора - одно для плеера (а он требует себе не только workspace, но и ещё кусок scratchpad - ценной 16-разрядной RAM, из-за чего не удалось увеличить скорость выполнения кода переносом его частей туда), второе для скроллинга и третье для всего остального (счётчики в цикле, координаты курсора и пр.)
В итоге суммарный размер intro получился в районе 22кб. Основная часть, конечно, это несжатая графика (12kb) и RLE сжатая музыка (1.8кб). Таким образом, сам код занимает где-то в районе 6-7кб (да и то там есть фрагменты, которые использовались лишь для отладки).
ПРИЛОЖЕНИЕ 2 - интро Speechtro
Однокилобайтное интро под названием
"Speechtro" (исходник) было написано вскоре после 99tro и представлено на DIHALT'2016 (где заняло первое место в 1kb low end intro).
В основе были две идеи:
1). Сделать что-то, использующее синтезатор речи (среди владельцев TI-99/4a он весьма распространён, а также хорошо поддержан эмуляторами - MESS/MAME и js99er).
2). Попытаться изобразить визуально большое число цветов с имитацией растровых эффектов.
После некоторых колебаний было решено обе идеи объединить в одну работу.
Речь использует слова прошитые в ПЗУ синтезатора (так как в случае с прямым LPC сжатием в 1кб столько речи не влезло бы). Поскольку их в ПЗУ весьма немного, ушло порядочно времени, чтобы составить из имеющихся слов связную речь на подходящую тему. К сожалению паузы в словаре синтезатора не предусмотрено. В качестве паузы я использовал довольно случайный адрес, данные с которого звучат как "бульканье". Это, во-первых, экономит и упрощает код, во-вторых звучит интереснее, чем просто тишина.
Что касается изображения, то первоначально хотелось попробовать реализовать растровые эффекты (меняя цвет фона в момент, когда луч идёт по нужной строке). Это оказалось технически невозможным (на скриншотах выше можно видеть попытки сделать это на настоящем TI и на эмуляторах - MESS и js99er), поэтому был выбран другой вариант - имитировать растровые эффекты через манипуляции с памятью атрибутов. В Graphics Mode 2 каждый блок 8x1 может иметь независимый цвет фона и цвет изображения, поэтому каждая полоска высотой в 1 пиксел может иметь разные цвета для фона и текста. Несмотря на то, что цветов всего 15 (а в полосках используется лишь 10), создаётся впечатление, что их гораздо больше.
Отдельные трудности вызвал вывод текста. Особенности конструкции TI-99/4a таковы, что ПЗУ (так называемый GROM) не отображается в адресное пространство процессора, поэтому данные для вывода шрифта извлекаются из GROM только последовательно (побайтно) - заданием начального адреса и чтением одной и той же ячейки памяти.
Выбранный набор "small caps" имеет только буквы A-Z (без цифр), что несложно заметить :)
Звёзды, как и ползущий наискосок баг, реализованы спрайтами 8x8.
В процессе проговаривания слов в двух линейках квадратиков отображается (условно) часть адреса текущего слова в ПЗУ синтезатора.
ССЫЛКИ НА РЕСУРСЫ ПО TI-99/4a
Здесь можно посмотреть мои работы под разные ретро-платформы, а
здесь их исходники на github.
P.S. За железку спасибо Кириллу Тимофееву и Тимуру Ташпулатову (tnt23)
Пользуясь случаем, хочу также пригласить всех интересующихся на фестиваль
Chaos Constructions, который мы ежегодно (с прошлого века) проводим в Питере в конце августа, и где можно потрогать разные ретро-компьютеры живьём.