Pull to refresh

Архитектура и программирование Fairchild Channel F

Abnormal programming *Assembler *Demoscene Old hardware
«Channel F homebrew would be like programming sprites via hardware jumpers...»
/ chadtower, atariage forum /




Игровая приставка Fairchild Channel F, также известная как VES, появилась в ноябре 1976 года. В отличие от своих предшественников типа Ping-Pong, Tennis (в том же ряду — советский «Видеоспорт»), у неё было очень существенное отличие — наличие микропроцессора и картриджей с программами. До этого игры в приставках реализовывались на жёсткой логике — программа, в современном понимании, там отсутствовала.

Fairchild Channel F выпускалась вплоть до 1983 года. За это время было продано более четверти миллиона этих приставок и выпущено около 30-40 игр, некоторые из которых — уже в 2000-х годах.

Говоря о первенстве в плане использования микропроцессора стоит отметить, что RCA Studio II, о которой я рассказывал в прошлой статье, опоздала всего на пару месяцев, но оказалась существенно слабее Channel F, ввиду чего и провалилась по продажам. Впрочем, появление, менее чем через год, Atari VCS — вытеснило с рынка и Channel F.

Развития Fairchild_Channel_F, как такового, не было. В System II и нескольких клонах, типа Saba Videoplay 2 (1979), отличия состояли преимущественно в корпусе, джойстиках (кстати, все они понимали кроме обычных положений ещё и поворот ручки) и количестве микросхем. Архитектурно всё было практически идентично.

Что же собой представляет Channel F?

Процессор


Процессор, в традициях эпохи, производился той же компанией Fairchild, что и сама приставка и называется F8 en.wikipedia.org/wiki/Fairchild_F8. Это 8-разрядный процессор 1974 года выпуска, работающий на частоте 1.8МГц (одна инструкция занимает от 1 до 6 тактов).



Микропроцессором его можно назвать, однако, лишь с натяжкой, поскольку состоит этот процессор из двух чипов — вычислительного устройства 3850CPU, содержащего АЛУ, аккумулятор, 64 байта SRAM, логику необходимую для выполнения инструкций, два порта (шины адреса нет!) и 3851PSU (Programmable Storage Unit), который содержит 1кб ПЗУ (используется для BIOS), указатель команд, схемы адресации памяти, прерываний, таймер (прерывания и таймер конкретно в Channel F не используются).

Позднее появился чип F3859, который объединял CPU и PSU на одном кристалле и Mostek 3870 — чуть усовершенствованная версия, выпускавшаяся вплоть до 1990-х. Впрочем, это уже отдельная история. Интересно, что иногда F8 называют микроконтроллером (в частности, из-за наличия таймера и портов) и упоминают как предка семейства Intel MCS-48 (8048).

Помимо Channel F, процессор F8 использовался также в компьютере VideoBrain и в шахматном компьютере CompuChess. В целом удивительно, что об этом процессоре очень мало информации. За исключением пары описаний от Fairchild и исходников нескольких игр и примеров конкретно для Channel F — больше ничего толком и нет. Такое впечатление, что этот процессор больше нигде не использовали, что кажется невероятным (учитывая, что его продвинутые версии выпускались довольно долго). Предположу, что все остальные устройства, где он применялся — были военного назначения.

Теперь немного об особенностях процессора. Наводят на размышления набор и функции его регистров. Думаю, это одна из самых замороченных из существовавших в то время архитектур (наверное современные могут потягаться, но сравнение будет некорректным, т.к. они рассчитаны на компиляторы. С F8 же работали живые люди).

Регистров в процессоре много. Помимо 8-разрядного аккумулятора A и регистра флагов W (I,O,Z,C,S), имеется ещё «scratchpad» — 64 восьмиразрядные ячейки с различными функциями.

Первые девять ячеек используются как регистры общего назначения R0-R8 в командах типа 'lr a, 7" (загрузить содержимое регистра R7 в аккумулятор). Обратите внимание, что буква R не указывается — пишется лишь номер регистра. Что именно имеется ввиду, число или номер — должно быть ясно из контекста. Скажем, в случае с инструкцией lr чисел просто не может быть. А если это, допустим, «li 7» (загрузить константу в аккумулятор), то там это точно число, а не регистр.

Ячейки 9, 10-11 (H), 12-13 (K), 14-15 (Q) предназначены для сохранения других регистров в разных, типа вызова подпрограмм, ситуациях.

Регистры R16-R63 доступны только через специальный индексный регистр ISAR (Indirect Scratchpad Address Register), как шесть восьмибайтных буферов, которые образуются из ячеек 16-23, 24-31, 32-39, 40-47, 48-55, 56-63.

Шестиразрядный ISAR разделён на две части по 3 разряда. При увеличении или уменьшении ISAR на единицу, затрагиваются только младшие 3 разряда — т.е. адресация осуществляется в пределах одного из шести упомянутых восьмибайтных буферов (конец буфера при этом можно отследить специальной инструкций условного перехода br7).

	clr		; 0 -> A

; set ISAR to full address Ox27 (octal 27)

	lisu 2		; buffer N2
	lisl 7		; index within buffer N2

loop:	
	lr d,a		; A-> buffer N2[index], than decrement ISAR (27, 26, 25, ... )
	br7 loop	; go further if low part of ISAR contains 7 (end of buffer N2). if not, go loop again

В данном случае «d» в инструкции «lr d,a» — это не название регистра, а признак того, что ISAR надо будет уменьшить («i» — увеличивать, «s» — оставлять неизменным).

Есть и другой способ косвенной адресации — через регистр DC0 (Data Counter):

	dci data_addr       ; data_addr -> DC0
	lm		; [data_addr] -> A, DC0 + 1 -> DC0
	...
data_addr:
	db 0,1,2,3,...

Если нужно переносить данные из одной области памяти в другую, то дополнительно используется команда xdc, которая меняет местами содержимое регистров DC0 и DC1. Т.е. читаем из адреса на который указывает DC0, потом делаем xdc и пишем по адресу, в который указывает теперь уже DC1. Потом снова xdc, и т.д. Т.е. DC1 — это некий теневой регистр для хранения копии DC0. Напрямую к нему обращаться (кроме как командной xdc) — нельзя.

Приведённые примеры иллюстрируют лишь часть возможностей в плане адресации, на самом деле их больше.

Также в F8 имеются четыре порта — 0,1,4,5, в которые можно писать командой out и читать командой in. В Channel F порты используются для вывода графики, звука и чтения состояния джойстика.

На прочих инструкциях типа арифметики, переходов и прочего особо останавливаться не буду — они достаточно стандартны. Отмечу лишь, что выбор регистров над которыми можно производить действия очень ограничен, поэтому код быстро разрастается из-за необходимости постоянно перемещать значения туда-сюда.

Отсутствует вычитание (есть только сложение). Инструкции уменьшения (ds) и увеличения (inc) на единицу — несимметричны. ds работает только с регистрами r0-r8, inc — только с аккумулятором.

Безусловный переход портит аккумулятор.

Пример обычного цикла:

	li	25			; (r4) number of iterations 
	lr	4,a
next:
	ds	4			; r4--
	bnz	next			; until r4 == 0

Подпрограммы


Поскольку в F8 нет аппаратного стека, имеются сложности с вложенными вызовами подпрограмм. Обычный вызов и возврат выглядит так:

      ; ...code
      pi sub                  ; Pushes address of next instruction to PC1
                              ; address of sub is stored in PC0 (jump to subroutine)
      ; ...code continues

  sub:
      ; ... often used code
      pop                     ; Move return address from PC1 to PC0

Здесь PC0 — это обычный указатель команд. PC1 — так называемый «регистр стека». К стеку он отношения не имеет, просто в него сохраняется PC0 при вызове подпрограммы.

Если из sub вызывается ещё одна подпрограмма, адрес возврата затирается и нужно усложнять (сохранять первый адрес возврата в регистр K):

  prog:
      ; ...do something...
      pi sub1                 ; Address of next instruction stored in PC1
                              ; sub1 is stored in PC0 (jump to subroutine)
      ; ...do more...
  sub1:
      lr k,p                  ; Copy PC1 to K, original jump address to K
      ; ...do something...
      pi sub2                 ; Pushes address of next instruction to PC1
                              ; sub1 is stored in PC0 (jump to subroutine)
      ; ...do more...
      pk                      ; Store address of next instruction in PC1
                              ; Copy value in K to PC0 (jump back to main)
  sub2:
      ; ...do something...
      pop                     ; Move return address from PC1 to PC0

Если же нужен ещё и третий уровень вложенности, то всё становится совсем грустно (в BIOS для этого есть даже две специальных подпрограммы — pushk и popk).

Общая рекомендация — стараться заменять вызовы подпрограмм макросами. Конечно, если нет жёстких ограничений на размер кода.

Аппаратного стека в F8 нет. При необходимости он реализуется программно — через ISAR и буферы.

Запись в ОЗУ не очень актуальна (за неимением такового), но выглядит так:

	li	$FF	; set value
	dci	$3800	; set target address
	st		; write

Память


Хотя это прозвучит несколько странно, но ОЗУ в Fairchild Channel F нет. Имеющиеся два килобайта видеопамяти (MK4027) не отображаются в адресное пространство, да и вообще — недоступны для чтения, запись в них осуществляется через порты. Регистры же микропроцессора, хоть их аж целых 64, за ОЗУ считать вряд ли корректно.

Исполняемая программа хранится в сменных ROM картриджах, чаще всего имеющих ёмкость 2кб (некоторые современные игры используют 3к, 4к и 5к картриджи). Кроме того есть, встроенное в микросхему процессора, ПЗУ BIOS объемом 1кб, содержащее простую игру типа Tennis, пару-тройку полезных подпрограмм и образы нескольких символов.

В адресном пространстве BIOS расположен с $0000 по $07ff, ПЗУ картриджа — с $0800.

Графика


Графические возможности Channel F весьма примитивны, поскольку видеоконтроллера в виде отдельного чипа там просто нет — всё реализовано на обычной логике типа сдвиговых регистров, вентилей и операционных усилителей. Довольно необычно, что в разных источниках упоминается разное разрешение, причём вариантов — множество. Дело в том, что имеющиеся 2 килобайта видеопамяти подразумевают разрешение 128 x 64, но в реальности это далеко не так. Во-первых, многое зависит от того, какая область формируемого изображения на данном конкретном телевизоре видна (из-за чего первые 4 столбца официально вообще не используются). Во-вторых, последние несколько столбцов используются под палитру. В третьих, часть памяти не используется вообще.

В итоге — фактическое разрешение можно примерно оценить как 95 x 58 пикселов при 8 цветах (что, впрочем, сильно лучше, чем RCA Studio II с её черно-белыми 64x32).

Большинство приставок выпущено в NTSC варианте, однако PAL тоже существуют. Практических различий, как пишут, нет (количество строк то же самое).

По существу, такое простое железо позволяет лишь рисовать на экране точки. Хотя общее количество отображаемых цветов — 8, однако в пределах одной строки могут быть отображены лишь четыре (можно условно назвать это палитрой). Палитра устанавливается индивидуально для каждой строки, причём хранится в довольно странном месте — в столбцах 125 и 126 каждой из строк (которые в любом случае находятся за пределами видимой области). Изменение палитры производится, соответственно, рисованием пикселов в этих двух столбцах.


(жёлтым выделены область видеопамяти, которая фактически видна на экране и область, где устанавливается палитра)

Как уже отмечалось выше, записать данные в VRAM можно лишь одним способом — через порты. При этом указывается цвет, столбец, строка:

X записывается в порт 4, Y в порт 5, цвет в порт 1, после чего записью константы в порт 0 осуществляется передача данных:

	; set color (2 bit per pixel)
	li	$00	; color ($00 = green, $40 = red, $80 = blue, $C0 = background)
	outs	1

	li	104	; X
	com
	outs	4

	; set the row
	li	61	; Y
	com
	outs	5

	; transfer data to VRAM
	li	$60
	outs	0
	li	$50
	outs	0

	; wait for update
	lis	6
delay:	
	ai	$ff
	bnz	delay

Задержка требуется, чтобы всё успело записаться, иначе следующая точка может не нарисоваться. В официальной версии к столбцу и строке ещё добавляется 4 (для простоты опущено)

Заполнить весь экран точками (в цикле) занимает таким образом порядка секунды. Важно отметить, что к чрезвычайно медленному обновлению экрана добавляется ещё и то, что нет способа подождать обратного хода луча по кадру. Соответственно, даже небольшая перерисовка неизбежно сопровождается мерцанием.

Теперь о палитре. Строго говоря, для каждой строки есть два режима — чёрно-белый (когда фон чёрный, а передний план — только белый, какой бы пиксел не нарисовали) и цветной.

В цветном режиме цветов переднего плана всегда три — красный, зелёный, голубой (rgb). Плюс один из трёх цветов фона — серый, светло-синий, светло-зелёный.

Для установки палитры в столбцы 125 и 126 нужно записать следующие значения:

x=125	x=126	palette
---------------------------------------------------------------------
00	00	COLOR: rgb, light green bg
00	ff	COLOR: rgb, light blue bg
ff	00	COLOR: rgb, gray bg
ff	ff	B/W: www, black bg

В типовых играх обычно поступают так: сначала задают некий общий фон, для чего можно воспользоваться готовой процедурой BIOS:
	li	$c6		; $21 - b/w palette, fill with black. $c6 - color palette, fill with gray
	lr	3, A
	pi	clrscrn		; clrscrn BIOS call

Затем, при необходимости, делают полосу с чёрно-белой палитрой (к примеру, для вывода счёта игры белыми цифрами на чёрном фоне)


И далее уже ставят пикселы одного из трёх цветов (если фон серый, соответственно — r,g,b). В итоге, цветов-то конечно 8, но поставить в конкретное место точку произвольного цвета так вот запросто — нельзя. Вот набор картинок, которые дают некоторое представление о цветах и их сочетаниях.

Собственно, на этом вся графика, как таковая, заканчивается — всё, что хочется делать, делается рисованием пикселов, причём вручную. Из полезных подпрограмм, в BIOS есть вывод символов. Однако, из соображений экономии места, в ПЗУ имеются лишь изображения цифр и отдельных знаков, размером 5x8 точек:


Тем не менее, процедура их вывода полезна и может быть использована следующим образом:

	li	25			; column
	lr	1,a
	li	25			; row
	lr	2,a
	li	%11000000                 ; e.g. $c0 - green "0" 
	lr	0,a               	; a -> r0 
	pi	drawchar            ; call subroutine

В вышеприведённом варианте старшие два разряда в регистре r0 определяют цвет (10 — red, 11 — green, 01 — blue, 00 — transparent), остальные — порядковый номер символа ( 0 1 2 3 4 5 6 7 8 9 G? T SPACE M X BLOCK: — center|| left|| ` ), начиная с нуля. В регистры r1 и r2 помещаются, соответственно, столбец и строка.

Более практичный вариант, чтобы не думать о разрядах:

	li	20		; x
	lr	1,a		; a -> r1
	li	10		; y
	lr	2,a		; a -> r2
	li	$40		; char color in bits 6,7: $80 (%10000000) - red, $c0 (%11000000) - green, $40 (%01000000) - blue, $00 (%00000000) - transparent
	oi	1		; index of char ( e.g. 3 for "3" )
	lr	0,a		; combined color + char index -> r0
	pi	drawchar	; call subroutine        

Чтобы немного прочувствовать платформу, я написал для конкурса tiny intro на Chaos Constructions'2019 256-байтное интро. Ничего особенного, но обратите внимание, для чего там использовано построчное изменение палитры. Ползущая горизонтальная полоска как бы подсвечивает строчку, которая под ней находится, временно заменяя весь фон на чёрный, а все пикселы на белые. Поскольку для этого не надо перезаписывать сами пикселы (и не надо их потом за собой восстанавливать), можно делать такую «подсветку» очень быстрой и без мерцаний.

Второй момент — буквы «CC». Поскольку букв «C» нет в BIOS, использовано наложение друг на друга букв GG и цифры 1, чтобы получить инверсные «CC».



Звук


Со звуком всё плохо. Официально есть три звука — 120Гц, 500 Гц и 1 КГц. Фактически, получить что-то кроме щелчков и сдавленного писка — проблематично. Вдобавок, говорят что между PAL и NTSC машинами, равно как и между старыми и новыми версиями звук ещё и отличается. Впрочем, для типовых игр — вполне достаточно. Включается и выключается звук через порты:

    li   %01000000                 ; 1khz beep $40
    outs 5

    li   %10000000                 ; 500hz beep $80
    outs 5

    li   %11000000                 ; 120hz beep $c0
    outs 5

; some pause

    clr
    outs 5                          ; turn off sound

Народ развлекался даже проигрыванием музыки. Лучшие образцы отдалённо напоминают PC-Speaker. Правда, практического смысла всё равно нет — все ресурсы процессора уходят на музыку, ничего особо уже не порисуешь.

Средства разработки


В настоящее время существует готовая подборка необходимого софта под названием «Development Pack».

Туда входит ассемблер DASM, дизассемблер, эмулятор MESS (с отладчиком). Всё это без проблем работает как минимум под Windows 7.


Параметры для ассемблирования и запуска:

dasm.exe test.asm -f3 -otest.bin
messd channelf -cartridge %cartPath%\test.bin -w -effect sharp -r 640x480 -ka

Эмулятор вполне неплох, хотя отладчик там чрезвычайно странный. Новую версию MAME/MESS мне настроить сходу не удалось (заметил, что настроить MAME под непопулярную платформу, которую он, якобы, поддерживает — каждый раз нетривиальная задача).

В эмуляторе предполагается, что разрешение видимой области соответствует minx=5,minY=5, maxX=105,maxY=61

Поскольку нет никакого удовольствия что-то писать под эмулятор, не опробовав результат вживую, пришлось решать проблему с эмулятором ПЗУ. По моей просьбе tnt23 сделал специальный картридж (Александр Новожилов напечатал к нему корпус), в который вставляется EEPROM 28C16A. Из-за особенностей адресации F8, пришлось ещё покупать на eBay древнюю микросхему Fairchild 3853. В итоге, появилась возможность, запрограммировав EEPROM, смотреть, как выглядит код на живой машинке.


Кроме того, tnt23 приделал к Channel F S-Video выход (штатно её можно было подключить лишь к телевизору через антенный вход), что сильно улучшило качество изображения и цветопередачу.

Рассказ про Fairchild Channel F:



Ресурсы


Tags:
Hubs:
Total votes 52: ↑50 and ↓2 +48
Views 5.1K
Comments Comments 10