テキストおよびグラフィック VRAM についての覚え書き


img
PC80/88 系の VRAM/TVRAM について書きます。
ハードの進化と共に建て増しされていった部分なので、設定項目が多岐にわたって結構面倒くさいです。

非常に今さら感漂う記事で、構造だけならエミュレータのソースコードを見れば十分なのですが、
「何に使われたか」や「どう使われたか」を理解する助けにもなると思うので書き記しておくことにします。

主題が VRAM/TVRAM だけあって文字が多いです。

修正・追加は随時。


■ テキスト VRAM

まず注意点ですが、テキスト VRAM のアトリビュートについて、再現しないエミュレータがあるということを挙げておきます。
実用上は問題ないですが、特殊な使い方をしているソフトも若干あるので、実験の際はこの点を留意しておきましょう。

基本的に「どこにどんな値を書き込むか」というだけの話(BASIC でいうところの OUT や POKE 命令)なので、
構造を理解するという点では BASIC でも機械語でも大して変わりはありません。
妙な制限が何に由来するか、など知っておくとオトクだと思います。

表題の、ややこしく感じる理由ですが「モード」や「設定」が色々あるのが原因だと思います。
しかしながら実際によく使われるのは一部だけなので、まずはその状態にもっていくことを考えます。
80x25 カラーテキスト・デジタル 8 色・200ラインが概ね標準だと思います。
ただ、これらを設定するのは CRTC を弄らないといけないので、ちょっと後回し。


まずはメモリーマップから。
便宜上 V1 = PC-8001 と PC-8801/mk2 と 88SR 以降の V1S モードの総称とします。V1H は V2 と同じと見なします。



PC-8801mk2SR 以降になって、V1 モードと V2 モードに分かれましたが、後者ではテキスト VRAM がバンクメモリになりました。

アドレスが $F3C8〜 と、えらく中途半端な位置ですが、(80 桁 + 40アトリビュート) * 25行 = 3000byte ($BB8) とすると、
$F3C8 + $BB8 = $FF80 ということで、終わりの部分に揃えてあるということが分かると思います。
ちなみに N-mode では開始位置 $F300 ぴったりです。
このテキスト VRAM 開始アドレスの $F3C8 は変更することができます(後述)。

V2 モードでは、同じアドレス領域に「メインRAM」「テキストVRAM」「グラフィックVRAM-RGB」で 5 つのバンクが同居するという
(しかも全て役割が別々)事態になっています。バンクメモリ絡みのバグが起きやすいところでもあります。

V2 モードでは I/O ポート $32 の bit4=0 とするとテキスト VRAM に切り替わり、bit4=1 とするとメイン RAM になります。
この I/O ポートは設定の書き込みと設定した値の読み込みが出来るので、他のビットを弄らないように保護しておくのが無難です。


; V2 以前は不要
    in    a,($32)
    push  af
    res   4,a
    out   ($32),a
      ここにテキスト VRAM に対する処理を書く   
    pop   af
    out   ($32),a


テキストVRAM は「メイン RAM の」別バンク扱いのようなので、後述するグラフィック VRAM を選択した状態では I/O ポート $32 を操作しても無効です。

何故 V2 モードでテキスト VRAM が分離したのかというと、ひとえに遅かったからです(たぶん)。
V1 モードでは、ディスプレイの表示サイクルになると、メイン RAM と一体のテキスト VRAM に DMA コントローラがアクセスするために
メイン CPU が止められてしまい、その結果プログラムの実行速度が損なわれる結果になっていました。

V1 モード時代のゲームなどでテキストを用いないグラフィックだけのソフトでは、このテキスト表示のサイクルを止めてしまって
CPU が邪魔をされずに最高速を出せるように工夫したりしていました。
テキスト表示を止めるだけなら BASIC(V1S) でも実験できます。


10 TIME$="00:00:00"
20 OUT &H51,0
30 FOR I=0 TO 10000:NEXT     
40 OUT &H51,&H20
50 WIDTH 80,25
60 PRINT TIME$


このまま run した時と、out 命令をコメントアウトしたときの時間を比較してみると分かると思います。

DMAC の方を操作する方法もあり、I/O ポート $68 に 0 を出力してテキスト DMA を止める例も見かけます。
これらのテキスト非表示は、表示再開する場合には手順を踏まないと同期が乱れて画面がおかしくなります。
筆者が V2 モードで実験していた際には、リセットボタンすら効かなくなることがありました。

DMA が CPU の実行速度に影響を及ぼすという事実は、BEEP 音での演奏にも悪影響が出ます。
CPU は BEEP の on/off を一定間隔で繰り返すことで音程を作り出しているので、DMA によって邪魔されると間隔が一定でなくなってしまい、ノイジーに聞こえたり違和感が出ます。
そこで、N88 BASIC では cmd sing に "X0" コマンドや cmd text off コマンドによって 明示的に DMA をオフにできるように拡張されました。
タートルグラフィックなどと同じ拡張コマンド扱いで、これによって BEEP 音でのクリアな演奏が保証されるようになりました。
原理的にはこれもポート $51 に 0 を書き込んでいるだけです。


SR 以降になって分離されたテキスト VRAM の領域は「高速 RAM」と呼ばれるようになったようです。
高速 RAM のアドレスの範囲は $F000 - $FFFF の間で設定できます。
V1 モードでは分離されていないので、上記の制限無く任意の場所をテキスト VRAM にできます。


次はこのテキスト VRAM の中身について。



1 行に付き 120byte です。なのでアドレス計算的には TextAdr = 0xF3C8 + y * 120 + x みたいな感じになります。
画面に表示される部分が 80x25 で、その右側にアトリビュートエリアがある、ようなイメージです。
テキスト VRAM の方は、文字アスキーコードを書き込みます。セミグラフィックモードの時にはドット配置パターンになります(後述)。

アトリビュートは基本的に「その行のテキストの装飾を指定する」ものです。装飾は色や点滅など。
ここは 2byte 一組になっていて 40byte なので「一行につき 20 回まで属性が適用できる」ことになります。
80 文字なのに 20 回まで?という気もしますが、そういうものです。
例えば、ある文字列を赤文字にしたら、元に戻すときにもアトリビュート操作をする必要があります。足りねー。

2byte 一組と書きましたが、フォーマットは「適用開始 X座標指定(0-79)」「適用装飾指定」の順です。
以下は、その装飾指定の表。

白黒モードカラーモード(色指定)カラーモード(装飾指定)
bit70=キャラクタ 1=セミグラフィック bit7色指定G bit70
bit60 bit6色指定R bit60
bit50=アンダーラインOff 1=On bit5色指定B bit50=アンダーラインOff 1=On
bit40=アッパーラインOff 1=On bit40=キャラクタ 1=セミグラフィック bit40=アッパーラインOff 1=On
bit30 bit31=色指定 bit30=装飾指定
bit2装飾指定bit2(別表) bit20 bit2装飾指定bit2(別表)
bit1装飾指定bit1(別表) bit10 bit1装飾指定bit1(別表)
bit0装飾指定bit0(別表) bit00 bit0装飾指定bit0(別表)

bit210装飾効果
111($07) リバースシークレット
110($06) リバースブリンク
101($05) リバースシークレット
100($04) リバース
011($03) シークレット
010($02) ブリンク
001($01) シークレット
000($00) ノーマル

bit7(G)6(R)5(B)色指定
111($E8) 白
110($C8) 黄色
101($A8) 水色
100($88) 緑
011($68) 紫
010($48) 赤
001($28) 青
000($08) 黒

装飾効果は bit2=反転、bit1=点滅、bit0=消去 です。
()内はアンダー/アッパーラインなしの装飾指定のアトリビュート値です。
「ブリンク」は点滅、「リバース」は反転。ちなみに点滅間隔は後述の CRTC リセット後のパラメータ設定で 4 段階に変更できます。
「シークレット」は分かり難いですが、黒塗り(リバース時は反転色)で表示され、キャラクターコードは拾えるが見えない状態になります。
単にカラー指定の黒で表示したときは上にかぶせたカーソルも見えなくなりますが、シークレットの場合はカーソルは見えます。

アンダー/アッパーラインはブリンクの影響を受けません。文字本体だけが点滅します。

 

アンダー/アッパーラインはリバースの影響を受けます。ライン部分が黒線になります。


アンダー/アッパーラインはシークレットの影響を受けません。文字本体だけが黒塗りになります。



白黒とカラーの切り替えは後述。キャラクタ=文字です。
カラーモードのとき、bit3=1 なら色指定、bit3=0 なら装飾指定になります。これによって他のビットの解釈が変わります。

RGB 8 色も一応表にしておきます。()内の値はキャラクタモード時のアトリビュート値になります。
このテキスト色はグラフィック VRAM の設定が「カラー」の場合はデジタル 8 色固定(パレット無効=I/Oポート$54-$5B のパレット設定はグラフィック専用)、
「白黒」の場合かつアナログモードのときは PC88 のアナログパレットを反映した 8 色が適用されます。
装飾の実際の効果については、実験してみるのが一番速いです。V1 の BASIC なら起動直後 POKE &HF491,2 などでブリンク効果が出ます。

さてここで大事な掟があって、
  1. 色(bit3=1)と装飾(bit3=0)は別々に指定しなければならない。
  2. 同じ開始位置で別のアトリビュート適用はできない。必ず開始位置をずらして適用する。
  3. 最大 20 回のアトリビュート指定では、必ず若いX座標から適用されるようにしなければならない。
  4. 行の最初のアトリビュート開始位置は何を書き込んでも強制的に X=0 と見なされる。
  5. 属性は属性で打ち消す。色は色で打ち消す。
正直、面倒くさいです。

1 は例えば「赤い」「点滅」をやろうとすると、2 回分のアトリビュートを使用すると言うことです。しかも 2 の制限により、開始位置はずらさないとダメ。
反対に、アンダー/アッパーラインと点滅などは同時に指定できます。
4 は、一番上の行で言うと、$F3C8+80=$F418 が最初のアトリビュート適用開始位置指定ですが、ここに何を書いても X=0 扱いになります。

ここで「適用順をデタラメにしたらどうなるの?」とか「属性として不正な値を入れたらどうなるの?」という疑問が当然わくとおもいますが、
「おかしなことになる」としか答えられません。筆者も法則性についてはよく分かっていないのでした。
どうやら「開始位置」と「属性値」をセットではなく別々に拾っているような感じ。

2 のようなものは各自で気をつけるとして、他の制限についてはサブルーチンで自動的に処理してくれるようなものを使った方が便利です。
というわけで、それっぽいものを置いておきます。vramatr.asm
a に適用したい色または属性、b=y c=x を入れて call すると、順番などを考慮してアトリビュートを設定します。

追記。色々試したところによると、

・開始位置は自動でソートされた上で順番に適用される
・属性値は最初から順番にピックアップされる

ということのようです。
開始位置を 0, 70, 60, 50, 40, 30, 20 と敢えて逆順にして(先頭はどうせ x=0 固定)
属性値を $28,$48,$68,$88,$A8,$C8,$E8 と 書き込むと、
0=$28, 20=$48, 30=$68 40=$88, 50=$A8, 60=$C8, 70=$E8 という結果になります。
結局混乱するだけなので自前でしっかり管理する方がよさそうですが。


テキスト画面は 40x20, 40x25, 80x20, 80x25 の 4 種類の桁x行を選択できます。
この場合、テキスト VRAM がどういう使われ方になっているかというと、
N-BASIC では width 72 や width 36 も出来るのですが、これは単に BASIC での表示範囲を制限しているだけで、実際には 80 や 40 です。
「POKE &HF300,&H61」などと打ち込んでみるとちゃんと額縁部分にも表示されることがわかります。

起動直後のアトリビュートは [0x80][0x00] で初期化されているようです(白黒モード)。
BASIC で CONSOLE 0,25,0,1 (1=カラーモード)とすると [0x80][0xE8] で初期化されます。



V2 モード専用のゲームを立ち上げると、このように文字の間が空いた状態でメッセージが出ることがありますが、
これは本体のディップスイッチ設定で 40 桁モードになっている場合を考慮してあるわけですね。



40桁モードでもちゃんと読める用になっている、と。


あまりそのあたりを考慮していないゲームなんかでは、このようになってしまうわけです。
同じ F 社のゲームなのに・・・

桁・行数を変える方法は CRTC を弄らないといけないので後述。


アトリビュートのついでにセミグラフィックについて。

セミグラフィックは 160x100 解像度で、PC-8001 の頃から使用できました。
基本的にテキスト画面に文字の代わりにドットパターンを表示しているだけなので、アトリビュートの適用も色・装飾共に文字の場合と同じです。
文字と混在できるので、セミグラ「モード」というほどでも無いです。ドット毎に自由に色をつけられるわけでもなし。

アトリビュートに開始位置とセミグラフィック指定ビット=1 を立てて、テキスト VRAM には文字コードの代わりにドットパターンを書き込みます。
40/80 桁の差も同じで、1文字に相当するグラフィックが横に太る代わりに奇数アドレスに書いたものは無視されます。



点灯する箇所のビット=1 として 1byte で 8 ドットを表します。


セミグラであってもアンダー/アッパーラインや点滅などの装飾はつけられます。

高解像度グラフィック画面しか使わない、と決めていてさえ PC80/88 系ではテキストアトリビュートと向き合わなければならない時がしばしばあります。
まして、セミグラフィックやテキストでゲームを作ろうなどと考えた日には、それ相応のライブラリを構築するのに一苦労することでしょう。
一つの考え方として、アトリビュートの 20 回制限は仕方ないとして色と装飾はどちらかをあきらめるか大幅に制限をつけるということです。
同じ位置から適用できないのは見た目に影響が大きい上にバグの温床にもなります。

ゲーム的な背景とキャラクタを合成する処理を考えると、アトリビュート処理は以下のような機能が要求されます。


つまり、色を変えたあとに元の背景色に復帰するようなアトリビュート操作が必要ということです。
これに加えて色が消えてはいけないプレイヤーに優先順位をつけたり、表示内容はそのままでアトリビュートのみを変更したりできるとなお良し。
一例として、普通のメッセージ表示用途に作ったものを置いておきます。これは色と装飾の両方を使えるようにするためやや重くなっています。
改造して書式付き fprint 的なモノにするのも良いでしょう。

putmsg.asm


■ CRTC と DMAC

CRTC と DMAC を弄る前に一通り I/O ポートを見ます。

I/O ポート $30 【入力】
bit7未使用
bit6未使用
bit5DELコード受信時動作
0=DELコードを処理
1=DELコードを無視
bit4Sパラメータ
0=Sパラメータ有効
1=Sパラメータ無効
bit3起動時のテキスト画面行数
0=25行
1=20行
bit2起動時のテキスト画面桁数
0=80桁
1=40桁
bit1起動時のモード
0=ターミナルモード
1=BASICモード
bit0起動時のBASICモード
0=N-BASIC
1=N88-BASIC
I/O ポート $30 【出力】
bit7未使用
bit6未使用
bit5USARTチャンネルコントロール
00=CMT600ボー
01=CMT1200ボー
10=RS-232C非同期
11=RS-232C同期
bit4
bit3CMTモーターコントロール
0=オフ
1=オン
bit2CMTキャリアコントロール
0=マークキャリア(2400Hz)
1=スペースキャリア(1200Hz)
bit1CRTモードコントロール
0=カラーモード
1=白黒モード
bit0CRT桁数コントロール
0=40桁
1=80桁
入力の方は、ディップスイッチで設定した起動時の初期設定 80/40桁、20/25行などを拾えます。
出力は bit0 で 40/80 桁の変更ができます。CRTC の桁数設定と同時に設定します。
この I/O ポートを設定するだけでは行数は変わりません。

bit1 の白黒/カラー変更はテキスト色(アトリビュートで設定)ですが、 CRTC の設定と同じ設定しないと効果があらわれません。

I/O ポート $31 【入力】
bit7BASICモード
0=V2モード
1=V1モード
bit6スピード
0=標準モード(V1S)
1=高速モード(V1H)
bit5通信方式
0=半二重
1=全二重
bit4Xパラメータ
0=有効
1=無効
bit3ストップビット長
0=2ビット
1=1ビット
bit2データビット長
0=8ビット
1=7ビット
bit1パリティ指定
0=偶数パリティ
1=奇数パリティ
bit0パリティチェック
0=チェック有り
1=チェックなし
I/O ポート $31 【出力】
bit7未使用
bit6未使用
bit5専用高解像度CRT におけるラインコントロール
0=20行
1=25行
bit4グラフィック画面カラー設定
0=白黒
1=カラー
bit3グラフィックディスプレイモード
0=グラフィック画面を表示しない
1=グラフィック画面を表示する
bit2ROMモード
0=N88-BASIC
1=N-BASIC
bit1RAMモード
0=ROM/RAMモード
1=64KB RAMモード
bit0グラフィックモード
0=640x400x1
1=640x200x3

入力の方は表示に関するものは一切ないですが一応載せておきます。

bit4 のディスプレイカラー設定はグラフィック画面の方の設定です。テキストは直接的には無関係(白黒かつアナログ色の場合は関係あり)。
bit0 の 640x400x1 の設定はグラフィックの B プレーンを上半分、R プレーンを下半分とする 400 ライン構成になります。G は表示されません。
この設定は高解像度(24KHz)モニターで、かつ bit4=0(白黒)設定でないと有効になりません。

テキストに関して、ちょっとややこしいのが bit5 で、テキスト行数の 20/25 行設定ができます。
CRTC の行数設定と同時に設定します。この I/O ポートを設定するだけでは行数は変わりません。
I/O と CRTC で双方反対の設定にすることが一応可能です。
その場合どうなるかというと・・・


CRTC で 25 行設定、I/O ポート $31 で 20 行設定の場合はこうなります。
「画面全体では 25 行に収まる」と「文字の縦幅は 20行の設定」が同時に実現します。

このスクリーンショットは筆者がペイントで作ったものです。



CRTC で 20 行設定、I/O ポート $31 で 25 行設定の場合はこうなります。
「画面全体では 20 行に収まる」と「文字の縦幅は 25行の設定」が同時に実現します。

このスクリーンショットは筆者がペイントで作ったものです。


I/O ポート $32 【入力・出力】
bit7音源割り込みマスク
0=割り込み許可
1=割り込み不可
bit6グラフィックVRAMアクセスモード
0=独立アクセスモード
1=ALUアクセスモード
bit5カラーモード
0=デジタルモード
1=アナログモード
bit4高速RAM/メインRAMバンク選択
0=高速テキストRAM選択
1=メインRAM選択
bit3画面出力モード
00=テレビ/ビデオモード
01=なし
10=アナログRGBモード
11=オプションモード
bit2
bit1内部EROM選択
00=内部EROMバンク0選択
01=内部EROMバンク1選択
02=内部EROMバンク2選択
03=内部EROMバンク3選択
bit0

ポート $32 はよく使います。
後述するグラフィック画面周りの設定が bit6,5 にあり、V2 では起動直後がアナログになっているのでデジタルで再設定したりします。
V1 モード時代のゲームを起動すると妙に画面が青っぽくなる現象は、このアナログ起動が原因なのでした。
デジタル用のパレットを初期化したつもりが実はアナログモードなので、デジタル色(0-7)が青の階調表現(0-7段階)になってしまうのです。

bit3,2 は 10 のままでいいと思います。何に使うのかよくわからない。一応他の設定も実機でテストしてみましたが、変化ありませんでした。
bit1,0 は 起動直後は 01 になっているようです。
EROM にはグラフィックやサウンドなど、主に new cmd で使えるようになる BASIC の新しい命令の処理が入っています。とりあえず関係ないので略。

I/O ポート $40 【入力】
bit7未使用
bit6未使用
bit5CRTCからの垂直帰線信号
0=ディスプレイサイクル
1=垂直帰線サイクル
bit4カレンダクロックからのデータ
bit3DISKブートモード
0=DISKから起動した
1=DISKからの起動ではない
bit2CMTデータキャリアディテクト信号
0=キャリアオフ
1=キャリアオン
bit1高解像度CRTモード信号
0=高解像度CRTモード
1=標準CRTモード
bit0プリンタからのBUSY信号
0=プリンタREADY
1=プリンタBUSY
I/O ポート $40 【出力】
bit7ポート波形を出力(88mk2以降)
bit6汎用出力ポート
bit5BEEP(2400Hz)コントロール
0=BEEP Off
1=BEEP On
bit4グラフィックハイスピードモード(V1Sのみ有効)
0=スタンダード
1=ハイスピード
bit3CRT I/F 同期コントロール
0=同期ビットオフ
1=同期ビットオン
bit2カレンダクロック用シフトクロック
0=クロックオフ
1=クロックオン
bit1カレンダクロックへのストローブ信号
0=ストローブオフ
1=ストローブオン
bit0プリンタへのストローブ信号
0=ストローブオン
1=ストローブオフ

入力の bit5 はよく使います。いわゆる V-Blank 期間を待つために、このビットを監視します。後でサンプルを示します。
そのほか、400ライン・200ライン(24KHz/15KHz)のモニターの接続状況を見るのに bit1 を見ます。
出力の bit3 は CRTC 設定の時に弄るのですが、どういう意味をもつものなのかよくわかりません。
出力の bit4 はおそらくサイクルスチールのオンオフ。SR 以降の機種は VRAM へのアクセスそのものが高速化されました。

BASIC にも「高速書き込みモード」にする命令(パラメータ)があります。
V1S モードにおいて SCREEN 命令の第二パラメータを 1 にすると I/O ポート $40 の bit4=1 になります。
スタンダードなら 34 秒かかるところ、高速書き込みモードなら倍速の 17 秒で終わるようになります。

10 'V1S graphic hi-speed mode
20 SCREEN 0,1,0,0:CLS 3
30 '        ^ change to 0(standard)
40 TIME$="00:00:00"
50 FOR I=1 TO 7
60 LINE(0,0)-(639,199),I,BF
70 NEXT
80 PRINT TIME$


I/O ポート $50 【出力】
bit7-0CRTC へのパラメータ出力

I/O ポート $51 【出力】
bit7-0CRTC へのコマンド出力

ようやく CRTC。これがテキスト設定の本丸です。
コマンドが $51 でパラメータが $50 です。間違いやすいので気をつけましょう(自戒)。

DMAC も一緒に掲載しておきますが、CRTC 設定と一緒に使うので後述します。

I/O ポート $60,62,64,66(DMA ch0-3) 【入力・出力】
bit7-0DMA 開始アドレスL
bit7-0DMA 開始アドレスH

I/O ポート $61,63,65,67(DMA ch0-3) 【入力・出力】
bit7-0ターミナルカウント L
bit7-6動作モード
00=ベリファイ
01=ライト
10=リード
bit5-0ターミナルカウント H

DMA のチャンネル 0-3 に対して 16bit のアドレスを書き込んだり、14bit のターミナルカウントを設定・取得したりします。
テキスト画面に関する部分でユーザーが設定できるのは DMA チャンネル #2($64,$65) です。
開始アドレスは 16bit でテキスト VRAM の開始位置。デフォルトでは $F3C8 になっています。N-mode では $F300。
ターミナルカウントは 14bit で、転送量-1 を設定します。テキスト VRAM なら 3000-1 など。
動作モードにはリードを設定します。

転送開始アドレスが V1S/V1H-V2 で設定できる範囲が異なります。
V1S: $0000-$FFFF で、V1H-V2 では $F000-$FFFF です。
ただし、$0000 のアドレスを含んだ場合は表示がおかしくなります。たとえば V1S で $0000- は NG ですが、$0001- は Ok です。
おそらく DMA の内部処理に関係していると思われます。
なお $0000-$7FFF の拡張 RAM をバンクセレクトした状態では拡張 RAM 側はテキスト VRAM にはなりません。メインバンク側が映ります。

転送開始アドレスと転送量に関しては、ちょっと面白いことができるので後述します。この頁の最下段アラカルトのところもご覧ください。

フリップフロップなので、書き込む度に下位 8bit →上位 8bit →下位のように切り替わります。
次に示す $68 へ書き込むとリセットされて下位からになります。

I/O ポート $68 【入力】
bit70
bit60
bit50
bit4UPDATEフラグ
bit3TCステータスビット ch.3 (テキストのオートロードバッファ)
bit2TCステータスビット ch.2 (テキスト)
bit1TCステータスビット ch.1 (8'DMAディスク)
bit0TCステータスビット ch.0 (5'DMAディスク)
I/O ポート $68 【出力】
bit7オートロード
bit6ターミナルカウントストップ
bit5拡張ライト
bit4回転優先
bit3ch.3 イネーブル(テキストのオートロードバッファ)
bit2ch.2 イネーブル(テキスト)
bit1ch.1 イネーブル(8'DMAディスク)
bit0ch.0 イネーブル(5'DMAディスク)

DMA のパラメータを $60-$67 で設定して $68 でコントロールするという感じです。
DMA というのは CPU の代わりに転送を代行してくれる機能で「開始アドレス」はそのままの意味、「ターミナルカウント(TC)」は転送量に相当します。
VRAM に書き込んだ内容が毎フレーム勝手に表示されているように見えるのは DMA 転送の仕業なのです。

コントロールポートの内訳を見てもどう操作したらいいのか分からないと思うので、後で具体例を示します。後回しが多いなぁ。


材料が揃ったところで、CRTC と DMAC に書き込みます。
まず、注意しないといけないのが垂直帰線期間(V-Blank:Vertical Blank)中に操作をするということです。
表示している最中に弄ってしまうとよろしくないことになるのは自明の理。


WaitVBlank:
    in	    a,($40)
    and     $20
    jr      nz,WaitVBlank           ;bit5 一旦ディスプレイサイクルになるまで待つ
.wait:
    in      a,($40)
    and     $20                     ;bit5 ディスプレイサイクル → VBlank になったら ret  
    jr      z,.wait
    ret


と、その前に。
PC88 には V-Blank は割り込みもあります。
割り込みの場合は、割り込み処理内でフラグを立てて、それを割り込み外から監視するというのが簡単だと思います。
V-Blank 割り込みと↑のサブルーチンを同時に使うと、↑のサブルーチンが取りこぼすことがあるので、どちらか片方にした方が良いでしょう。
関係は大ありなのですが、話が長くなるので割り込み関係の詳細や I/O ポート関係の話題にはここでは触れません。

I/O ポート $68 は入力も出来、UPDATE フラグと TC ステータスが拾えます。
詳細やタイミングについては i8257 のデータシートを検索すると載っています。
概略としては UPDATE は ch.3 から ch.2 へ転送パラメータをコピーする際に 1 になり(オートロードモード)、
TC については DMA の転送終了時に 1 になるという感じです。
テキスト DMA では TC2 のみ検出できます。
TC は一旦 1 の状態を read すると即座に 0 にリセットされます。UPDATE はしばらく持続。

実機で計測すると 1 になる期間はどちらも vblank とは被っていません。
しかし TC2 をポーリングすることで DMA の転送終了を拾えるため、vblank を待つよりも効率よくメモリアクセスのタイミングを測れて
ちらつきを抑えるのに使えるかもしれません。


CRTC には uPD3301 という IC が使われています。興味があればデータシートを読むとよいでしょう。
コマンドとパラメータは V-Blank を待った後、上で示した I/O ポート $51 にコマンド、$50 にパラメータを書き込み(または読み込み)ます。
表中 R/W で R は読み込み、W は書き込みの意味。

コマンド名R/Wビット備考
76543210
RESET コマンドW00000000コマンド $00 -> I/Oポート$51
パラメータ1WC/BH6H5H4H3H2H1H0パラメータ*5 -> I/Oポート$50
パラメータ2WB1B0L5L4L3L2L1L0
パラメータ3WSC1C0R4R3R2R1R0
パラメータ4WV2V1V0Z4Z3Z2Z1Z0
パラメータ5WAT1AT0SCA4A3A2A1A0

リセットコマンドを投げた後、パラメータを I/O ポート $50 の方に 5 バイト続けて書き込みます。
複数のパラメータが 8bit * 5byte 中に詰め込まれているので、一つ一つ見ていきます。

C/B0=DMAバーストモード
1=DMAキャラクタモード

必ず 1 = キャラクタモードにする、とあります。
一応・・・バーストモードにしてみるとテキスト画面が表示されなくなりました。

H6-H01行あたりの文字数-2

80 文字の場合は 78 で設定します。それ以上の指定は禁止な模様。
というか 80 文字以外の設定は出来ないような気がします。筆者の所ではモニターにリセットが掛かりました。

B1-B000=カーソル点滅(16) アトリビュート点滅(32) 速い
01=カーソル点滅(32) アトリビュート点滅(64)
10=カーソル点滅(48) アトリビュート点滅(96)
11=カーソル点滅(64) アトリビュート点滅(128) 遅い

カーソルは C1-C0 次第で点滅するかどうか設定できます。その点滅時間をここで設定。
アトリビュートは上の方で書いた「ブリンク」設定の点滅時間です。
カッコ内の数字が小さい方が点滅が速いです。デフォルトは 10 の設定。

L5-L01画面あたりの行数-1

25 行のときは 24 を設定します。
I/O ポート $31 にも設定がありますが、こちらと設定を合わせましょう。

S0=通常表示
1=一行おき表示

0 でいいです。

C1-C000=点滅しないアンダーラインカーソル
01=点滅するアンダーラインカーソル
10=点滅しない■カーソル
11=点滅する■カーソル

点滅する場合、間隔は 4 段階の変更が可能(B1-B0)。
↓の文字を構成するライン数が 13 以下の場合はアンダーラインが無効になるのだとか。

R4-R0文字あたりのライン数-1

1文字を構成する縦ドット数。
ドット数-1 で指定します。ドット数は 3〜32 の範囲。
この設定は 15KHz と 24KHz で違います。また、20 行と 25 行でも違います。
25 行の場合、15KHz では 8-1=7 24KHz では 16-1=15 を設定します。
20 行の場合、15KHz では 10-1=9 24KHz では 20-1=19 を設定します。

V2-V0垂直帰線幅-1

V-Blank 期間を行数換算で指定できるということのようです。
行数-1 で指定します。行数は 1 〜 8 の範囲。
この設定は 15KHz と 24KHz で違います。また、20 行と 25 行でも違います。
25 行の場合、15KHz では 7-1=6 24KHz では 3-1=2 を設定します。
20 行の場合、15KHz では 6-1=5 24KHz では 2-1=1 を設定します。

Z4-Z0水平帰線幅-2

H-Blank 期間を文字数換算で指定できるということのようです。
文字数-1 で指定します。文字数は 6 〜 33 の範囲。
この設定は 15KHz と 24KHz で違います。20 行と 25 行では同じです。
20/25 行ともに、15KHz では 32-1=31 24KHz では 26-1=25 を設定します。

H/V-Blank 期間に関しては、http://www43.tok2.com/home/cmpslv/Pc80/Pccrt.htm (Enri's Home PAGE)
>VRTC = 水平周波数 ÷ (垂直周波数 x 1 文字のライン数) − 1 画面に表示する行数
>HRTC = 発振周波数 ÷ (水平周波数 x 8) − 1行に表示する文字数
だそうです。

AT1-AT0,SC 00,0=トランスペアレント白黒・特殊制御文字有効
00,1=アトリビュート無し・特殊制御文字無効

01,0=トランスペアレント・カラー

10,0=ノントランスペアレント白黒・特殊制御文字有効
10,1=ノントランスペアレント白黒・特殊制御文字無効

上記以外は設定禁止。
テキスト画面の白黒/カラー設定はここで行います。
白黒は 000、カラーは 010 とします。
特殊制御文字を有効にした場合、各行2byte余分にメモリを必要とします。

特殊制御文字については READ STATUS の項目で。

A4-A01行あたりの最大アトリビュート数-1

アトリビュート数-1 で指定します。アトリビュート数は 1 〜 20 の範囲。
デフォルトは 19。悪名高いアトリビュート制限の元凶。
20 以上は指定禁止。特殊制御文字も含む。

コマンド名R/Wビット備考
76543210
STOP DISPLAYコマンドW00000000コマンド $00 -> I/Oポート$51

コマンドだけ書き込んでパラメータは書き込まないバージョン。
上の方で out &h51,0 をすると画面表示が止まるというのを書きましたが、まさにこれです。

このコマンドを実行すると、テキスト画面のみならずグラフィック画面も消えます。
表示を再開する時は同期ビットの操作なども必要です。crtcdmac.asm 参照のこと。


コマンド名R/Wビット備考
76543210
START DISPLAYコマンドW0010000DMI/Oポート$51

DM0=通常表示
1=反転表示


CRTC リセットコマンドでパラメータをセットした後、最後にこのコマンドを発行すると表示が開始します。
反転表示にすると、アトリビュートで「リバース」を設定した状態が「ノーマル」になります。逆もしかり。
全画面に効果があります。

コマンド名R/Wビット備考
76543210
SET INTERRUPT MASKコマンドW010000MEMNI/Oポート$51

MN0=画面終了時割り込み要求有効
1=画面終了時割り込み要求無効
ME0=特殊制御文字による割り込み有効
1=特殊制御文字による割り込み無効

DMAC をオートロードモードで使用する場合は両方とも 1 にする、とのこと。
オートロードモードというのは DMA ch.3 に設定した内容が ch.2 に自動的に反映される設定です。

特殊制御文字については READ STATUS の項目で。

コマンド名R/Wビット備考
76543210
READ LIGHT PENコマンドW01100000コマンド $60 -> I/Oポート$51
パラメータ1RHRCH6CH5CH4CH3CH2CH1CH0I/Oポート$50 -> パラメータ*2
パラメータ2R00ROW5ROW4ROW3ROW2ROW1ROW0

ライトペンの座標が読めるようです。ライトペン関係はよくわかりません。

コマンド名R/Wビット備考
76543210
LOAD CURSOR POSITIONコマンドW1000000CMI/Oポート$51
パラメータ1W0CH6CH5CH4CH3CH2CH1CH0パラメータ*2 -> I/Oポート$50
パラメータ2W00ROW5ROW4ROW3ROW2ROW1ROW0

CM0=カーソルを表示しない
1=カーソルを表示する

カーソルの位置はここで指定します。パラメータは X,Y の順。
カーソルの形状についてはパラメータ付きリセットコマンドを参照してください。

コマンド名R/Wビット備考
76543210
RESET INTERRUPTコマンドW10100000コマンド $A0 -> I/Oポート$51

DMA 要求を有効にするらしい。
リセットコマンドでパラメータを設定した後にこのコマンドを発行します。

コマンド名R/Wビット備考
76543210
RESET COUNTERSコマンドW11000000コマンド $C0 -> I/Oポート$51

内部カウンタをすべてクリアするらしい。

コマンド名R/Wビット備考
76543210
READ STATUSコマンドR000VEUNELPI/Oポート$51を読む

VE画面表示が有効。
UDMAアンダーラン(1行分の転送を完了できなかった)発生。発生した場合は画面表示は停止。
N特殊制御文字による割り込み発生。割り込み要求がアクティブになった。
E表示終了時の割り込み発生。割り込み要求がアクティブになった。
LPライトペン入力あり。座標がラッチされたことを示す。

何もコマンドを書き込まずに I/O ポート $51 を読むとステータスが読み込めます。
このうち VE は画面表示が有効な時は 1 を返します。OUT $51,0 などで画面表示を止めてしまうと 0 になります。
ほか、U,N,E,LP は 1 になるケースは通常無いと思います。
通常のオートロードモードで使う場合、SET INTERRUPT MASK コマンドで ME MN パラメータを共に「無効」にします。
これは E N に現れる割込み要求をマスクするという意味なので、やはり 1 になることはありません。

AT1-AT0,SC の項目でトランスペアレント・白黒にしたときに特殊制御文字有効設定にしますが、カラー・白黒ともに N=1 になることはありませんでした。

ちなみに特殊制御文字はアトリビュートエリアに書き込みます。
通常 1byte 目の開始文字桁の所を [0xE0 or 0x60] にして 2byte 目を以下のようにします。

bit70 固定
bit60 固定
bit50 固定
bit40 固定
bit30 固定
bit2 I: 特殊制御文字を検出時に割込み, ステータス N を 1 にする
bit1 V: 特殊制御文字を検出時から最終行まで表示停止
bit0 D: 特殊制御文字を検出時から最終行まで DMA 停止

V=1 にすると画面が点滅するようになります。D=1 にすると画面が消えます。
I=1 にした場合は何もおきません。
一見この特殊制御文字を有効にして書き込むとステータス N や E が立つように思えますが、実際は 0 のままです。
オートロードモードでテキスト画面を使うのであれば特殊制御文字には意味が無いということだと思います。


以上を踏まえて CRTC と DMAC を初期化するルーチンを置いておきます。BASIC での初期化手順とほぼ同じです。
このサブルーチンを呼ぶだけでは画面設定は変わりません。I/O ポート $30,$31 の桁・行・カラー/白黒設定を同時に行いましょう。
CRTC と I/O ポート設定はどちらが先でもいいようです。

crtcdmac.asm

忘れがちですが、テキスト VRAM のアトリビュート部が白黒モードで初期化されたままだと CRTC をカラー設定にしても画面は真っ暗なままです(逆もしかり)。
I/O ポート $30-$32、テキスト VRAM の初期化を併せて行うようにしましょう。

DMA の転送元アドレスは V2 モードでは高速 RAM なので $F000-$FFFF の範囲に限定されます。
V1 モードでは任意のアドレスに設定可。

20/25 行設定はソフトで変えられますが、15KHz/24KHz はそもそも繋がっているモニターのハードウェアの違いなので変更できません。
なので、15KHz/24KHz の場合分けだけは必ず必要になります。
15KHz と 24KHz の違いは単に 200/400 ラインの違いだけではなく、垂直同期周波数が 15KHz 時は約 55Hz、24KHz 時は約 62Hz になるようです。
こちら(http://www7b.biglobe.ne.jp/~crazyunit/pc88.html)で計測されています。

各パラメータは値の範囲が決まっていて、それ以外はダメということになっていますが、実際に値を書き込んでみたときにどうなるかは
筆者も良く分かっていません。最悪モニターが壊れるかも。垂直同期がずれるくらいなら笑って楽しめますが。
モニター次第でもあるので「こうすると確実にこういう効果が得られる」とは言い切れません。

普通に使う分には、せいぜいテキスト VRAM の初期化やカーソル表示を消したり、という程度にしか使わないと思うので
上記のルーチンのパラメータを適当に弄るだけで済みます。

応用例としては、DMA の転送開始位置を一行ずつずらすことによるスクロールなどが考えられます。

また、その昔マイコン BASIC マガジンに掲載された「MAGICAL COLOR」という PC-8001 用ゲームでは、8 色の画面 2 つを高速に交互に切り替えることで
27 色の同時表示(少しちらつきますが)を可能にしていました。PiO にも同様のプログラムがあったと思います。

原理はちょっとトリッキーで、DMA には転送開始アドレスと転送量を設定できるのですが、
この転送量を通常の 2 倍に設定することで、2 画面分が交互に表示されるようになるのです。
もちろん 2 画面分の、合成したときにちゃんと画面になるような VRAM データをあらかじめ設定しておかなければなりません。

偶数フレーム GRB 000 黒
奇数フレーム GRB 011 紫

この組み合わせと、

偶数フレーム GRB 011 紫
奇数フレーム GRB 000 黒

この組み合わせは同じ色ですが、以下の組み合わせも同じ色(暗い紫)になります。

偶数フレーム GRB 001 青 偶数フレーム GRB 010 赤
奇数フレーム GRB 010 赤 奇数フレーム GRB 001 青

全ての組み合わせから重複を除くと 27色。

27 色表示サンプルプログラムを置いておきます。ソースコード付き。
このプログラムではテキスト VRAM を $C000 に移動しています。その上で通常の倍の 6000byte DMA 転送する設定にしています。
V1 モード専用です。エミュレータでは再現性が下がりますが実機でもちらつきは激しいです。

color27_v1only.zip


このスクリーンショットは筆者がペイントで作ったものです。
エミュレータで綺麗に出せればいいんですけどねぇ。


グラフィック画面の I/O ポートも一部書きましたが、ALU 周りやパレットについての部分もあるので、それはグラフィックの部で追記します。
(参考出典:マシン語マスターバイブル 著:日高徹氏)


■ グラフィック VRAM

PC88 のグラフィック性能は、他のメーカーの機種に比べてあまり進化しませんでしたが
パレット切り替え可能な 8 色カラーや漢字など一通りの機能は揃っています。
ここでは主に SR 以降の機能を書きます。

基本的にグラフィックデータの読み書きはメモリ、モードの設定は I/O ポートです。
こちらでは、テキストの方で操作した I/O ポートはあまり弄りませんが DMA を停止するとテキストだけでなくグラフィック画面も表示されなくなるので注意しましょう。


VRAM メモリマップ。
400 ラインモードでは 200ライン分の VRAM を 2 つ使って 400 ラインを構成します。



400 ラインモードの方は B と R プレーンに色をつけていますが、画面への出力は白黒になります。B が上半分で R が下半分。
あわわ、これだと 200 ラインの方は 1/3 ずつ分担みたいに見えてしまいますが、もちろん RGB の合成色が出ます。

アドレス範囲は $C000-$FE7F 固定で、640x200 が 80byte * 200 のメモリ配置になっており、
RGB それぞれ 3 プレーンが同じアドレス空間のバンク切り替えとなっています。
ドットは 1byte(8bit) で 8 ドットを表現します。
400 ラインモードのときは B, R プレーン 2 つで 1 画面ですが、200 ラインと同様にメモリ範囲はそれぞれ $C000-$FE7F を使います。
画面が繋がっているからといって $FE80-$FFFF に何か書きこんでも表示はされません。

$FE80-$FFFF も表示されないだけで、同じく VRAM です。後述の ALU は、この画面外領域でも有効です。

切り替えの I/O ポートは $5C,$5D,$5F で、メインメモリ(とテキストVRAM)に戻すには $5F に出力します。出力する値はなんでも可。
この VRAM バンク切り替えを使ったアクセスを「独立アクセスモード」といいます。

640x200 の場合、ドットは縦長になります。640x400 だとほぼ正方形。

解像度・色のモードは以下のようになっています。
I/O ポート $31 の bit4=0 で白黒に、bit4=1 でカラーになります。
これが白黒の時、かつ bit0=0 なら 640x400 になります。つまり、400ラインモードは白黒のみということ。
また、当然ですが 400 ラインが表示できる 24KHz モニターが必要になります。
白黒モードは実は「白」と「黒」だけでも無いのですが、詳細は白黒の項で。

デジタルとアナログは I/O ポート $32 の bit5=0 でデジタル、bit5=1 でアナログになります。
こちらもアナログカラーが表示できるモニターが必要です。
当時は単色のモニターなんかも普通にあったのです。

さらに、SR 以降ではアクセスモードが二つあります。
この二つは同じく I/O ポート $32 の bit6=0 で独立アクセスモード、bit6=1 で拡張アクセスモードとなっています。
独立アクセスモードは V1 までと同じで、上に書いた I/O ポート $5C,$5D,$5E で BRG それぞれに切り替えて一つずつアクセスする方法です。

拡張アクセスモードは、SR 以降で新設された ALU 機能を使って、3 プレーンを同時にアクセスする機能です。
RGB のどのプレーンにアクセスするか、プレーンを選択して各々あるいは同時にアクセスすることが出来ます。

余談ですが、V1 モードでは VRAM を「選択しているだけで」実行速度が遅くなります。
メモリ空間の $C000-$FFFF がメイン RAM 以外の状態にあるときは実行速度が低下します。
以下のテストコードで実験できます。

110 CLEAR ,&HBFFF:DEFINT A-Z:'V1S
120 S=&HB000:E=&HB016:DEF USR=S
130 FOR A=S TO E:READ B$:POKE A,VAL("&H"+B$):NEXT
140 TIME$="00:00:00"
150 A=USR(0):PRINT TIME$:END
160 data F3,D3,5C,11,0A,00,21,00,00,2B,7D,B4,20,FB,1B,7B,B2,20,F6,D3,5F,FB,C9
170 '          ^^ change to 5F

65536*10 の空ループを実行するだけです。上記のまま(VRAM.B を選択した状態)だと 22 秒かかりますが、
メイン RAM (F3,D3,5F に書き換え)を選択した状態だと 6 秒で終わります。V1S のみで V1H/V2 では影響ありません。


I/O ポート。既に挙げたテキストの方にも関連する設定がありますが、ここでは主にグラフィック関連のものを掲載します。

I/O ポート $34 【出力】
bit7未使用
bit6ALU プレーン G に対する処理指定 H
bit5ALU プレーン R に対する処理指定 H
bit4ALU プレーン B に対する処理指定 H
bit3未使用
bit2ALU プレーン G に対する処理指定 L
bit1ALU プレーン R に対する処理指定 L
bit0ALU プレーン B に対する処理指定 L

ALU モードの際に、各プレーンに対してどの処理を適用するかを決定します。
処理は 4 種類あり、2bit で指定します。内容は以下。すべて論理演算です。
この I/O ポート $34 の設定を使用するためには、ポート $35 で「I/O ポート $34 の設定を使ったライト」の設定にしなければなりません。

HL処理内容書き込む値VRAM上の値結果
00not AND ビットリセット 000
011
100
110
01OR ビットセット 000
011
101
111
10XOR ビット反転 000
011
101
110
11NOP なにもしない 000
011
100
111

例えば B プレーンにビット反転をする場合は bit4=1 bit0=0 とします。
この論理演算は ALU モードで「書き込む値」との論理演算です。

not AND は「ビット反転した上で AND」の意味です。「マスク」に使える他、$FF を書き込むと VRAM 上のデータは全て $00 になります。
3 プレーン共に処理を not AND にして VRAM に $FF を 16000byte 書き込むことで高速に画面消去ができます。

not AND でマスクデータを書き込んで OR でキャラデータを合成する、というのがキャラクタなどを重ね合わせる上での常套手段です。

最後の「何もしない」というのも大事です。
これを指定することにより、特定のプレーンにだけ論理演算を適用することが出来るのです。

論理演算だけなので、逆に「そのまま上書きする」という設定がありません。この点が若干不便なところ。


I/O ポート $35 【出力】
bit70=メインRAMアクセス
1=グラフィックVRAMアクセス
bit6未使用
bit500=I/O ポート $34 の設定を使ったライト
01=3プレーンの分のリードデータを3プレーン同時ライト
10=RプレーンのデータをBプレーンへ転送
11=BプレーンのデータをRプレーンへ転送
bit4
bit3未使用
bit2Gプレーンの比較データ
bit1Rプレーンの比較データ
bit0Bプレーンの比較データ

ALU 機能の要。

bit7 でアクセス先を変えられますが、少しややこしいので注意しましょう。
この設定は「ALU が ON の時に」アクセス先を変えるものです。
ALU ON の時 $C000-$FFFF の空間は I/O ポート $5C-$5F の設定にかかわらずグラフィック VRAM になりますが、
この ALU ON の状態(I/O $32.bit6=1)を維持したまま、一時的にアクセス先を切り替えられる、という意味です。

つまり、I/O ポート $32 の ALU ON が前提であり、この bit7=1 に変えるだけではグラフィック VRAM はアクセスできません。
そして、bit7=0 でメインRAM を選択したとき、アクセスできるのは「メイン RAM かテキストVRAM」です。
I/O ポート $5C〜$5F の状態にかかわらず、グラフィック VRAM はアクセスできません。これらはあくまで独立アクセス用。
反対に I/O ポート $32 でテキスト VRAM が選択されている場合は、テキスト VRAM へのアクセスができます。

ややこしいので、筆者はこの bit7 は 常に 1 で使用しています。
メイン RAM やテキスト VRAM をアクセスしたい場合は、 I/O ポート $32 で ALU そのものをオフにするようにしています。


bit5,4=00 にすると上で説明した I/O ポート $34 の設定を使用した論理演算アクセスになります。
↓に詳しく書きましたが、こちらは書き込みに push 命令で 16bit 幅の書き込みを使っても大丈夫です。
バッファは関係ないので、push 命令では 16bit レジスタの中身が「8bit 単位で論理演算後、書き込み」という動作が下位・上位の 2 回分繰り返されます。

このモードは「ライト」とありますが、読み込みの場合は↓の 3 プレーン同時リードと同じ動作で、バッファに 3 プレーン分のデータが入ります。


bit5,4=01 の設定は VRAM から 1byte 読み込んだときに 3 プレーン分のデータをバッファに読み込み、
書き込む時には、バッファにため込んだデータを 3 プレーンに同時に書き込みます。
このモードは VRAM 同士のコピーなので、論理演算などは挟まず(I/O ポート $34 の設定は使わず)単純な上書きができます。スクロールなどに利用できます。

ここで「バッファに」という所がポイントで、ALU 拡張アクセスモードでは VRAM からの読み込みは、レジスタに格納されません。
レジスタに格納されるのは bit2-0 で設定できる「比較データ」であり、VRAM の中身はユーザーからは見えない 8bit 幅のバッファに格納されます。

8bit 幅なのでこの 3 プレーン同時アクセスモードでは、つぎのような方法は使えません。


    ld      a,%10010000     ;G.VRAMアクセス / 3プレーン同時読み書きモード    
    out     ($35),a
    ld      hl,($C000)      :2byteずつ読み込んだつもり
    ld      ($C002),hl      ;2byteずつ書き込んでいるつもり


以下のやりかたが正解。


    ld      a,%10010000     ;G.VRAMアクセス / 3プレーン同時読み書きモード
    out     ($35),a
    ld      a,($C000)      :1byte読み込み この a は VRAM の中身ではなくダミー。
    ld      ($C002),a      ;1byte書き込み こちらも、a の中身を書き込んでいるわけではない。


読み書きについては、その「動作」が大事、ということです。
このモードでは pop/push 命令も 8bit 幅バッファの中身を 2 回分読み書きしてしまうことになるので正常に働きません。

また、以下のようなやり方でも ALU バッファへの取り込みができます。


    ld      hl,$C000         
    cp      (hl)             


HL レジスタさえ適切に設定してあれば、A レジスタを破壊せずに、ALU バッファに取り込みできるウマい手です。


bit5,4=10,11 は、主に 400 ライン白黒モードで使います。200ラインやカラーモードでも一応使えなくはないですが実用性はなさそう。
10 の場合、読み込みを R プレーンから、書き込みを B プレーンに行います。
11 の場合、読み込みを B プレーンから、書き込みを R プレーンに行います。
400 ライン白黒の場合に読み=書きのアドレスにして LDIR 命令を使うと半画面単位のスクロールが簡単にできます。
独立アクセスモードの時は、一々バンクを切り替えないといけないので、B,R プレーンが同時にアクセスできると楽、という話。
とはいえ・・・使用頻度は低いです。


Bit2-0 は、比較データの設定です。上で説明したとおり、ALU モードでの読み込み動作でレジスタに入るのは「比較データ」です。
たとえば、bit2-0(GRB) を 011 = 紫にしたとします。
その上で、ALU モードで読み込みを行うと、レジスタには「読み込んだ VRAM アドレスのドットが紫の部分だけが 1、それ以外は 0 」になります。
つまり、8bit 幅 3 プレーン分の ALU バッファとの「比較」という意味なのでした。

これが何の役に立つかというと、ペイントをする際の境界領域の判定に使えるのです。
色の違う境界部分を検出することでペイントが高速化できます。
ちょっと頑張れば当たり判定に使うこともできそうです。


I/O ポート $52 【出力】
bit7未使用
bit6白黒モード時の背景色 G
bit5白黒モード時の背景色 R
bit4白黒モード時の背景色 B
bit3未使用
bit2ボーダーカラー G
bit1ボーダーカラー R
bit0ボーダーカラー B

あまり使用頻度は高くなさそうですが、グラフィックの白黒モード(200/400ライン共)時に「黒」の部分を任意の 8 色に変えることが出来ます。
I/O ポート $31 の bit4=0 の白黒の他に、$32 の bit5=0 デジタルモードである必要があります。
アナログで背景色を設定するにはポート $54 を使いましょう。

ボーダーカラーは PC-8801 初代機のみの機能で表示画面外の色を設定できたのですが、mk2 以降で削除されてしまいました。

背景色は「黒」の方ですが、白黒モードの「白」の部分は 200/400 ライン問わず、以下の特徴があります。
テキストがアナロググラフィックパレットの影響を受けるようになり、グラフィックがテキストアトリビュートの影響を受けるようになります。

白黒モード時に CRTC にリセットコマンドを送って止めると、「白」の部分はカラーコード 0 番の色になるようです。
アナログパレット 0 番に黒以外の色を入れておくと確認できます。

ちょっと分かりづらいのでテスト用のプログラムを挙げておきます。

10 new cmd ' V2
20 screen 1,0,0,7 'color graphic
30 console 0,24,1,1 '8col text
40 y=7
50 for p=0 to 7:line(p*50,0)-(p*50+50,100),p,bf:next
60 for p=1 to 7:cmd pal p,(p and 4)*y*16+(p and 2)*y*4+(p and 1)*y:next
70 cmd pal 0,3*64+2*8+1

上記のプログラムで、カラーグラフィックで以下のようにアナログ色設定されることを確認します。


続けて、以下のように入力します。

screen 0,0,0,7       
out &h51,0


白黒グラフィックになり、


有意なドット部分(青〜白でボックスフィルされた領域)がアナログカラーパレット 0 番で設定された色になります。


I/O ポート $53 【出力】
bit7未使用
bit6未使用
bit5未使用
bit4未使用
bit3白黒モード時 G プレーンを 0=表示する 1=表示しない
bit2白黒モード時 R プレーンを 0=表示する 1=表示しない
bit1白黒モード時 B プレーンを 0=表示する 1=表示しない
bit0テキスト画面を 0=表示する 1=表示しない

グラフィック画面の表示・非表示設定は白黒グラフィックモードの時のみ有効。400ラインの時は G プレーンは無関係です。
テキストの表示・非表示はカラーグラフィックモードでも有効です。
後述の通り、白黒グラフィックモードでは、パレットを操作して特定のプレーンを表示したり非表示にしたりできないので
この I/O ポートを使って同等の処理を行います。

なお、白黒グラフィックモードで、テキストアトリビュートを使って色を付ける場合、この I/O ポートでテキスト画面を消去したとしても
アトリビュートによる着色の効果はなくなりません。

(覚書:M88 は 400ラインの時、G プレーンを表示する設定にしていると、R,B テキストプレーンも表示されてしまう)


I/O ポート $54-$5B(デジタル)【出力】
bit7未使用
bit6未使用
bit5未使用
bit4未使用
bit3未使用
bit2G プレーン 1=オン 0=オフ
bit1R プレーン 1=オン 0=オフ
bit0B プレーン 1=オン 0=オフ
I/O ポート $54-$5B(アナログ) 【出力】
bit700=R,Bプレーンの設定
01=Gプレーンの設定
bit6
bit5Rのbit2
bit4Rのbit1
bit3Rのbit0
bit2B(G)のbit2
bit1B(G)のbit1
bit0B(G)のbit0
カラーコードの 0 〜 7 に対応する $54 〜 $5B の 8 個の I/O ポートがグラフィックのパレット設定です。

デジタル・アナログで同じ I/O ポートを使用します。
デジタルは I/O ポート $32 の bit5=0、アナログは bit5=1 で決定されます。
V2 モードは起動直後はアナログモードになっています。

SR で拡張された I/O ポートは V1 モードでもアクセス出来てしまうのですが、アナログパレットに関しても V1/V2 モードで有効です。
テキストをカラーモード&グラフィックを白黒モードに設定すると V1/V2 に限らずテキストやセミグラフィックにアナログカラーを適用することが出来ます。
これに 32 行表示や 50 行表示を組み合わせると 160x100 ドットセミグラフィックに 2x3 や 2x2 ドット単位でアナログカラーを適用できたりします。
32/50 行表示についてはアラカルトの項で。
テキスト(セミグラ)+アナログ限定ですが背景色(ポート$54で設定)も変えられるので、画面全体としては 8+1 色が出せることになります。




白黒グラフィックモードの時は、$54 〜 $5B のパレット操作は無効です。
設定値自体は有効でカラーグラフィックに戻ったらきちんと反映されますが、白黒モードでは単に各 RGB プレーンのドットが 1 の所が点灯する動作になります。
従って、カラーグラフィックでよくある「パレットを弄って B プレーンのみを表示させる」ようなことはできません。
白黒モードでこのような処理をする場合は I/O ポート $53 をつかいましょう。


デジタルの場合は どう組み合わせても既存の 8 色にしかならないので、カラーコード → 色への対応を「読み替える」ような動作になります。

アナログは bit7,6=00 の時は bit5-0 に R と B の 3bit ずつの色設定値を、bit7,6=01 の時は bit2-0 に G の 3bit 設定値を書き込みます。
RGB で 3bit * 3bit * 3bit で 9bit = 全 512 色から 8 色を選べることになります。

パレット設定はデジタルモード(I/O ポート $32 bit5=0)の時にアナログ用の設定をすると、デジタル色として設定されてしまいます。
bit7,6=00 であるものとして設定されてしまうということです。

アナログパレット設定は若干面倒くさいので、設定ルーチンを用意しておくとラクです。


;アナログパレット設定 0-255 で ok
PalMacro:       macro R, G, B
    dw          $4000 | ((G >> 5) << 8) | ((R >> 5) << 3) | (B >> 5)
    endm

SetAnalogPalette:
;   call        WaitVBlank
    ld          c,$54                   ;$54-$5B
    ld          hl,.data
    ld          b,8*3
.loop:
    outi                                ;R,B bit76:00
    outi                                ;G   bit76:01
    inc         c
    djnz        .loop
    ret
.data:        ;   R    G    B
    PalMacro      0,   0,   0           ; 黒
    PalMacro      0,   0, 255           ; 青   B
    PalMacro    255,   0,   0           ; 赤   R
    PalMacro    255,   0, 255           ; 紫   B+R
    PalMacro      0, 255,   0           ; 緑   G
    PalMacro      0, 255, 255           ; 水色 G+B
    PalMacro    255, 255,   0           ; 黄色 G+R
    PalMacro    255, 255, 255           ; 白   G+R+B


パレット機能があることにより、8 色ながら単に色を変えられるという以上の効果を得ることができます。
デジタル 8 色でもフェードアウト的なことができたり、あえて同じ色を 2 つ以上設定して特殊(っぽく見える)効果を実現したり等々。
便宜上 RGB プレーンといっていますが、パレット機能により、たとえば B プレーンに何か書き込めば単純に「青」になる、わけではないという点は
押さえておくと良いと思います。


I/O ポート $54(アナログ背景色) 【出力】
bit710=R,Bプレーンの設定
11=Gプレーンの設定
bit6
bit5Rのbit2
bit4Rのbit1
bit3Rのbit0
bit2B(G)のbit2
bit1B(G)のbit1
bit0B(G)のbit0

デジタル・アナログパレットのカラーコード 0 と I/O ポートは共通ですが、白黒モードの背景色をアナログで指定できます。違いは bit7,6。
bit7,6=10 の時は bit5-0 に R と B の 3bit ずつの色設定値を、bit7,6=11 の時は bit2-0 に G の 3bit 設定値を書き込みます。
200 ラインでも 400 ラインでも、白黒モードであれば設定は有効です。
結局、白も黒もアナログ色に変えられるので「白黒」モードとは一体・・・という感じ。

SR で拡張された I/O ポートは V1 モードでもアクセス出来てしまうのですが、8 色あるアナログパレット同様に BG パレットも V1/V2 で設定できます。
上で述べたテキスト(セミグラフィック)にアナログカラーを適用する方法を応用すると 8+1 色を画面全体で出せます。
ただし V1 モードで電源 on するとアナログ BG パレットが初期化されないため、BG が真っ白に設定されることがあります。
一度でも V2 でリセットすれば大丈夫ですが、V1 でも手動で out $54,$80, out $54,$C0 すれば黒設定になります。


I/O ポート $5C 【入力】
bit7未使用
bit6未使用
bit5未使用
bit4未使用
bit3未使用
bit2Gプレーンが 1=選択されている 0=選択されていない
bit1Rプレーンが 1=選択されている 0=選択されていない
bit0Bプレーンが 1=選択されている 0=選択されていない
I/O ポート $5C 【出力】
bit7-6独立アクセスモードの時 B プレーンを選択する

I/O ポート $5D 【出力】
bit7-6独立アクセスモードの時 R プレーンを選択する

I/O ポート $5E 【出力】
bit7-6独立アクセスモードの時 G プレーンを選択する

I/O ポート $5F 【出力】
bit7-6独立アクセスモードの時、メインRAMを選択する

VRAM 独立アクセスモードのとき、I/O ポート $5C-$5F でアドレス空間 $C000-$FFFF のバンクを切り替えられます。
書き込む値は何でも OK。
そして、今どのバンクが選択されているかは I/O ポート $5C のリードで取得できます。
3 プレーンすべて選択されていなければ、メイン RAM と見なせます。
割り込みでバンクを切り替えて復帰する際などに必要になるかもしれません。

ALU モードにすると、この情報はリセットされメインメモリ選択状態になるようです。
ALU モードを解除しても RGB プレーン選択状態は復活しません。


画面周りは他機種との・他社との差別化が、文字通り『目で見て分かる』ので、技術競争が最も激しかった部分と言えます。
単純にキャラクタを描画する処理についても様々なテクニックが使われており、Z80 ならではかつ PC88 ならではの高速化が突き詰められました。
詳細は長くなるので省きますが、基本的にはここに記した機能をフルに使って実現されたものです。

一例としてキャラクタの重ね合わせつき表示について示します。

VRAM は RGB が 3 プレーンあり、そのドット配置パターンによる 8 通り = 8 色について、パレットが設定できます。
ということは 3 プレーンのうち、どれか一つ(あるいは二つ)を背景として設定して、残りのプレーンにキャラクタを表示すれば、
複雑な重ね合わせ処理をしなくてもハードウェアの仕様そのままで、背景+キャラの重ね合わせが実現出来ることになります。
この手法は初代 PC88 の時代によく使われました。

この例では背景を G プレーンに取り、キャラクタを R,B プレーンに取っています。
『背景をそのまま透過させたい部分』『背景にかぶせて表示したい部分』がパレット上どうなっているか、がポイントです。

どうでもいいけど、このキャラなんなんだ。



また、重ね合わせが必要ない場面でも、RGB 3 プレーンのうち、あえて 3 プレーンを全てつかわずに 2 プレーンだけ使うことことにより
処理を 2/3 にするなどの高速化・データの省サイズ化も実際に使われました。
デザインに工夫は必要ですが、大きなサイズのアニメパターンを高速に表示する場合などに有効です。

冒頭でも説明したとおり、V1 ではテキスト画面を表示しておくと遅くなるので、テキストを非表示にするテクニックもよく使われました。

最後に、ALU を使った場合と使わなかった場合の重ね合わせ付きキャラクタ表示サンプルを置いておきます。
V2 モード用ですが、V1 の場合は ALU 未使用の処理がそのまま使えます。
マスクパターンを用意すること、AND/OR などの論理演算を使うこと、が重要です。
ちらつきまくりですが、そんなものです。工夫のしがいがあるとも(強引に)言えます。

alu_test3.zip




■ その他 アラカルト




テキスト画面はグラフィック画面の上に表示されるので、グラフィックを隠す効果があります。
特に、黒色や装飾のシークレットで隠したい場所を■で埋めると何も見えなくなります。
その上で、隠れたグラフィックを表示したい場所へ向けて ALU 機能の 3 プレーン同時リード・ライトを使って転送すると
アニメーションなどが見栄え良く、かつ高速に実行できるというわけです。




400 ライン白黒モードでテキストアトリビュートカラーが適用できるのがどれだけ重要か、という最大の実例がこれ。
640x400 の漢字表示に色がつけられるのです。これで高機能なワードプロセッサまで実現していたのだから恐れ入ります。
Super 春望は使ったことないけれど、どうだったのだろう。
N88 日本語 BASIC?? そんなものは忘れた。



M88 で play6 プレイヤーがバグるのは、カラーモードの状態で I/O ポート $53 を操作すると、白黒モードに戻ったときに反映されないのが原因です。

このアプリケーションは 200ライン+白黒モードでテキストアトリビュートによるグラフィックへの色反映を行っているのですが、
RGB 3 プレーンのうち画面を構成しているのは B プレーンだけで、他は物置に利用しています。
なので R,G プレーンは I/O ポート $53 で表示しないようにしているのですが、カラー時に設定しているのでスルーされてしまっている模様。
もちろん実機では白黒に戻ったときに正しく反映されます。

ePC8801 や XM8 でもバグるのですが、それはまた別の話。テキストアトリビュート反映が未実装っぽい。

追記:M88fmgen や ePC8801 では修正された模様。

白黒200/400ライン、カラーグラフィック200ライン、白黒/カラーテキストの組み合わせは全部で6通りありますが、
具体的にどういう描画結果になるのか文章だけでは分かりづらいと思うのでテストプログラムを置いておきます。
V1 モード用です。

10 'V1 
20 T=0:G=1 'T:0=BW,1=COL / G:0=COL200 1=BW200 2=BW400
30 screen G,0,0,7
40 console 0,24,1,T
50 line(0,0)-(100,100),1,bf
60 locate 0,0:print"NEC PC-8801"
70 for a=0 to 19:poke &hf418+a*2,79:next
80 if T=0 then goto 130
90 'color text red+reverse
100 poke &hf418,0:poke &hf419,72:poke &hf41a,4:poke &hf41b,4
110 poke &hf41c,11:poke &hf41d,0:poke &hf41e,12:poke &hf41f,232
120 line(0,0)-(639,5),0:end
130 'bw text reverse
140 poke &hf418,0:poke &hf419,4:poke &hf41a,11:poke &hf41b,0
150 line(0,0)-(639,5),0:end
T と G の組み合わせで 6 通りの描画結果が変わります。


右の二つは T=0,G=2 とT=1,G=2 です。
一番左は白黒テキストの200ライン白黒グラフィックで、一見テキストの反転アトリビュートが効いていないように見えますが、
拡大するとスキャンラインがズレているように見える(=反転)のが分かります。"1"とその右がわかりやすいです。
また、横にラインを0(=黒)で引いたときにラインがどのように表示されるか、隠れたテキストがどのように表示されているかに注目してみてください。


筆者も最近まで知らなかったのですが、PC88 のパレットの反映は hblank 毎(?)に行われるようで、十分なパレット書き換え速度とタイミング調整をすれば
一画面中に 8 色の制限を超えることができます。一部エミュレータでは対応済み。



上の画面では r,g,b,w 各アナログ 8 段階を同時に表示することが出来ています。
ただし、このタイミングは CPU の速度(4/8MHz)、ウェイトの有無(S/H)、水平同期周波数 15/24KHz などでも変わるので調整は容易ではないです。
テストプログラムおよびソースコードは別ページの p8suite に入っています。(修練場->PC-8801シリーズ用テストツール集)

さて、こうなってくると画面描画途中に変更できる設定が他にもあるのではないか?という気がしてきます。

いろいろテストしてみます。
グラフィック画面に描いたテストパターンを、表示期間の真ん中あたりで 200 -> 400 ラインに切り替えてみます。
4MHz 時、1 ラインにつき 232clk くらいで計算して、100 ラインを描画し終わったあたりで切り替えています。数字はかなりアバウト。

果たして 200/400 ライン混在は可能か。

100 ' Binary to N88-BASIC(V2-4MHz)
110 CLEAR ,&H8FFF:DEFINT A-Z
120 S=&H9000:E=&H9078:DEF USR=S
130 FOR A=S TO E:READ B$:POKE A,VAL("&H"+B$):NEXT
140 CLS:'END
150 A=USR(0):END
160 data F3,3E,99,D3,32,CD,36,90,CD,28,90,CD,23,90,CD,2F,90,21,D6,04
170 data 2B,7D,B4,20,FB,CD,1E,90,18,EA,3E,2A,D3,31,C9,3E,3B,D3,31,C9
180 data DB,40,E6,20,28,FA,C9,DB,40,E6,20,20,FA,C9,D3,5C,21,00,C0,3E
190 data 80,01,C8,28,77,23,23,10,FB,06,28,0D,20,F6,D3,5D,21,00,C0,3E
200 data FF,01,19,50,11,30,02,77,23,10,FC,19,06,50,0D,20,F6,D3,5E,21
210 data 40,C1,3E,01,01,19,28,77,23,23,10,FB,19,06,28,0D,20,F5,D3,5F
220 data C9



可能なようです。画面下半分あたりから R プレーンのみで描画されています。白黒 400 ライン仕様通り。
アドベンチャーゲームでメッセージ部だけ 400 ライン漢字とか使えたりすると面白いかもしれません。

同様に、表示期間中にグラフィック画面 on -> off は可能かどうかテストしてみます。

100 ' Binary to N88-BASIC(V2-4MHz)
110 CLEAR ,&H8FFF:DEFINT A-Z
120 S=&H9000:E=&H9078:DEF USR=S
130 FOR A=S TO E:READ B$:POKE A,VAL("&H"+B$):NEXT
140 CLS:'END
150 A=USR(0):END
160 data F3,3E,99,D3,32,CD,36,90,CD,28,90,CD,23,90,CD,2F,90,21,D6,04
170 data 2B,7D,B4,20,FB,CD,1E,90,18,EA,3E,33,D3,31,C9,3E,3B,D3,31,C9
180 data DB,40,E6,20,28,FA,C9,DB,40,E6,20,20,FA,C9,D3,5C,21,00,C0,3E
190 data 80,01,C8,28,77,23,23,10,FB,06,28,0D,20,F6,D3,5D,21,00,C0,3E
200 data FF,01,19,50,11,30,02,77,23,10,FC,19,06,50,0D,20,F6,D3,5E,21
210 data 40,C1,3E,01,01,19,28,77,23,23,10,FB,19,06,28,0D,20,F5,D3,5F
220 data C9



これも可能なようです。まぁテキストマスクでやれば済む話ですけどね。

逆に駄目だったものは、、、
I/O ポート $32 のデジタル -> アナログ。
これは $54-$5B のパレットレジスタへの設定モードを変えるためのものであって、デジアナの表示方法を変えるものではないようなので予想通りでした。

I/O ポート $30 の 80桁, 40桁変更や CRT モードコントロールの白黒・カラー変更なども駄目でした。
これらはテキスト側の変更ですが、一律に画面が乱れ意図通りにはなりません。もしかしたら 400 ラインを基準にプログラム書くべきだったかも。


DMAC 設定のテキスト開始アドレスと転送量設定の組み合わせで 27 色表示が出来ることは本文中に示しましたが、
そのほかに行数設定を組み合わせて弄ることでセミグラフィックで 2x2 ドット単位の色付けが出来るようになります。
これは VIDEOKUN PC という雑誌掲載ゲームで使われた(このときは 32 行表示で 2x3 ドット単位)テクニックの応用です。



原理としては以下のようになります。
CRTC の設定で行数を 1-64 行まで設定できますが、PC80(88) が 200/400 ラインであることには変わりないので、
例えば 25 行の時に 1 文字は 400/25=16 ドットだったのが倍の 50 行のときは 400/50=8 ドットということになります。
これが実際の表示にどう影響するかというと、、、



文字のラスターラインのうち半分しか表示されなくなるのです。
これを意図的にセミグラフィックに応用すると、bit0-7 のうち bit0,1,4,5 のみ表示されることになります。
本来 1文字 = 2x4 ドットに対しカラーが適用されるところが 2x2 ドットに対しての適用となるのです。

CRTC には「文字を構成する縦ライン数」の設定もあるので同時に設定しておきます。
さらに 50 行表示にあわせてテキスト VRAM の開始位置と転送量を倍に増やしておかなければなりません。
表示するための絵作りも既存のものでは難しいと思います。

拙作 crtcdmac.z80 では水平垂直同期周波数や行数から各種数値を計算して自動設定するようにしています。
ソース付きのサンプルプログラムを置いておきます。

line50.zip


この行数を増やす技はモニタによっては同期に失敗する可能性があり、筆者の実機では 32 行設定のときに数秒ほどちらつきが止まりませんでした。
さらに、15kHz, 200ラインのときに設定値が一部オーバーフローする場合があり、正常に動作するかは確認が取れていません。


他に類する情報が中々見当たらなかったので、これを最後に実機を永久封印するつもりで調査してまとめてみましたが、
このページ内でも情報が散らばっていてわかりにくいかも。

筆者の実機の挙動はどうにも怪しく、アナログ色が正常に出ないときがあります。
そんな状態でテストしたので、このページの結果も鵜呑みにせず各自検証してもらえると幸いです。


▲ TOP