俺メモ: 俺と VGA と書きこみモード
さて、VGA の書きこみモードついてきちんと説明しつくした文献って実はほとんど存在しないということに気づいてしまった今日この頃…というのが今までのあらすじ。こんなとき頼りになるのはやはり原典。そう、IBM の技術解説書であります。ありがたいことに VGA のソフトウェアまわり(BIOS のことじゃなくて、プログラムで直接ハードウェアを叩く、という意味)に関しては OADG の技術解説書(ハードウェア編)で IBM の技術解説書とほぼ同等の情報を得られます。
OADG から落としてきたオンライン資料を持っている人は oadghwd.pdf を開いて、第3章にある「3.2.10 ビデオ・メモリー読み書き操作」にある図3-32「VGA メモリー書きこみ操作でのデータの流れ」を見てください。TTL 程度のハードウェア知識があればこれを注意深く辿ってみることで VGA の書きこみモードをいちおう理解できるようになります。
…いや正直な話、この図を真剣に見たのはおとといが初めてだったり。だって見るからにうざいっしょーこの図? わけわかんねーとか思ってました。
でまあ、言葉で説明すると予想以上にまだるっこしくなるので、同等の動作をする擬似コードを書いてみますた。
/* vga4pwm.c EGA/VGA 4plane 時の書き込みモードを擬似的に再現するコード vga_makeup4p(plane, value, latch, gr) plane VRAM 各プレーンへの出力結果。 unsigned char plane[4] の先頭ポインタ value 書き込むデータ latch 以前の読み込み操作でラッチに格納された値 unsigned char latch[4] の先頭ポインタ gr グラフィクスコントローラレジスタの内容 unsigned char gr[8] の先頭ポインタ おおざっぱな手順 : ・CPU からのデータを4プレーンに展開 Write Mode 0: 各プレーンごとにローテートした CPU データ/00h/FFh の いずれかを設定 Write Mode 2: CPU データを色番号とみなし、対応ビットのプレーンに 00h/FFh を設定 Write Mode 3: Set/Reset レジスタの内容を色番号とみなし、対応ビットの プレーンに 00h/FFh を設定(この時点で CPU データの内容は 使われない) ・展開したデータをラッチの内容と論理演算 ・Bit Mask レジスタの内容(Write Mode 3 のときは、ローテートした CPU データと Bit Mask レジスタを AND したもの)にもとづき、 各ビット位置ごとに演算結果とラッチのどちらの内容を使うのか決める Write Mode 1 のときは単にラッチからコピーしておしまい */ #define MAX_PLANE 4 static unsigned char ror_8(unsigned char b, int cnt) { unsigned short w; cnt &= 7; if (cnt == 0) return b; w = *1; } void vga_makeup4p(unsigned char *plane, unsigned char value, const unsigned char *latch, const unsigned char *gr) { const int max_plane = MAX_PLANE; unsigned char tmpplane[MAX_PLANE]; int write_mode, rotate_count, logical_function; unsigned char bitmask; int i; logical_function = (gr[3] >> 3) & 3; rotate_count = gr[3] & 7; write_mode = gr[5] & 3; /* Write Mode 1 のときは単にコピーして終了 */ if (write_mode == 1) { for(i=0; i<max_plane; i++) plane[i] = latch[i]; return; } /* Expand CPU-value (to 8bits x 4planes) */ switch(write_mode) { case 0: value = ror_8(value, rotate_count); for(i=0; i<max_plane; i++) { if (gr[1] & (1<<i)) { tmpplane[i] = (gr[0] & (1<<i)) ? 0xff : 0x00; } else { tmpplane[i] = value; } } bitmask = gr[8]; break; case 2: for(i=0; i<max_plane; i++) { tmpplane[i] = (value & (1<<i)) ? 0xff : 0x00; } bitmask = gr[8]; break; case 3: for(i=0; i<max_plane; i++) { tmpplane[i] = (gr[0] & (1<<i)) ? 0xff : 0x00; } bitmask = gr[8] & ror_8(value, rotate_count); break; } /* Logical operataion with latched data */ switch(logical_function) { case 0: /* SET/REPLACE */ /* unmodified */ break; case 1: /* AND */ for(i=0; i<max_plane; i++) { tmpplane[i] = tmpplane[i] & latch[i]; } break; case 2: /* OR */ for(i=0; i<max_plane; i++) { tmpplane[i] = tmpplane[i] | latch[i]; } break; case 3: /* XOR */ for(i=0; i<max_plane; i++) { tmpplane[i] = tmpplane[i] ^ latch[i]; } break; } /* Mask unmodified bits */ for(i=0; i<max_plane; i++) { plane[i] = (tmpplane[i] & bitmask) | (latch[i] & (~bitmask)); } }
…合ってるのかね、これ。
*1:unsigned short)b << 8) | (unsigned short)b; return (unsigned char)(w >> (8 - cnt