|
|
nono 0.1.6
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
#include "bitmap.h"
#include "bt454.h"
#include "cgrom.h"
#include "mpu.h"
//
// ビットマッププレーン
//
// Bitmap の VRAM はロングワード単位でホストバイトオーダ配置なので、
// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。
//
// SX-9100 のほうはこう書いてあってこっちが正しいようだ。
// +00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
// b100 |RF| |BS| |共 通 B M P|プレーン#0 |
// b110 |プレーン#1 |プレーン#2 |プレーン#3 |プレーン#4 |
// b120 |プレーン#5 |プレーン#6 |プレーン#7 |FC| |
// b130 |F0| |F1| |F2| |F3| |
// b140 |F4| |F5| |F6| |F7| |
//
// SX-7100 のほうはこう書いてあるがこうはなっていないようだ。
// +00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
// b100 |RF| |BS| |共 通 B M P|FC| |
// b110 |プレーン#0 |プレーン#1 |プレーン#2 |プレーン#3 |
// b120 |プレーン#4 |プレーン#5 |プレーン#6 |プレーン#7 |
// b130 |F0| |F1| |F2| |F3| |
// b140 |F4| |F5| |F6| |F7| |
//
// RF: RFCNT (4バイト)
// BS: BMSEL (4バイト)
// FC: 共通ファンクションセット
// Fn: プレーン#n ファンクションセット
// 1区画が 64KB。空白のところは未調査。
// 未装着のプレーン(#4..#7とか)は 0xff が読めるようだ。
// ファンクションセットが 64バイトで折り返してるか未調査だが、
// b130'0000 も b133'0000 も 0xff が読めるのでこの範囲全域でミラーかも。
// b150'0000 以降(b1ff'ffffまで) は BUSERR のようだ。
//
std::unique_ptr<BitmapDevice> gBitmap;
BitmapDevice::BitmapDevice()
{
logname = "bitmap";
devname = "Bitmap";
devaddr = 0xb1000000;
monitor_size = nnSize(42, 11);
}
BitmapDevice::~BitmapDevice()
{
}
bool
BitmapDevice::Init()
{
// XXX とりあえず。実際は設定による
nplane = 1;
// プレーン数によってメモリを確保
mem.reset(new uint8 [0x40000 * nplane]);
return true;
}
bool
BitmapDevice::PowerOn()
{
// XXX 実際は不定値だと思うけど、観測手段がないのでとりあえず。
memset(&mem[0], 0xff, 0x40000 * nplane);
return true;
}
void
BitmapDevice::ResetHard()
{
// 厳密にどの時点でリセットされるのかは知らないけど
bmsel = 0;
// XXX 初期値の観測は困難なので適当
rfcnt = 0;
hscroll = 0;
vscroll = 0;
for (int i = 0; i < MAX_PLANES; i++) {
fcs[i] = 5;
mask[i] = 0xffffffff;
}
// XXX ここ?
Invalidate();
}
uint64
BitmapDevice::Read8(uint32 addr)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
if (addr < plane0base) {
// 書き込み専用
return 0xff;
}
if (addr < plane0base + 0x40000 * nplane) {
// プレーンの読み出し
return mem[HLB(addr - plane0base)];
}
// 残りは書き込み専用
return 0xff;
}
uint64
BitmapDevice::Read16(uint32 addr)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
if (addr < plane0base) {
// 書き込み専用
return 0xffff;
}
if (addr < plane0base + 0x40000 * nplane) {
// プレーンの読み出し
return *(uint16 *)&mem[HLW(addr - plane0base)];
}
// 残りは書き込み専用
return 0xffff;
}
uint64
BitmapDevice::Read32(uint32 addr)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
if (addr < plane0base) {
// 書き込み専用
return 0xffffffff;
}
if (addr < plane0base + 0x40000 * nplane) {
// プレーンの読み出し
return *(uint32 *)&mem[addr - plane0base];
}
// 残りは書き込み専用
return 0xffffffff;
}
uint64
BitmapDevice::Write8(uint32 addr, uint32 data)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
switch (addr) {
case 0xb1000000:
case 0xb1000001:
case 0xb1000002:
case 0xb1000003:
// このロングワード内へのバイトアクセスはロングワードにミラーされる
data = (data << 24) | (data << 16) | (data << 8) | data;
return Write32(addr & ~3, data);
case 0xb1040000:
case 0xb1040001:
case 0xb1040002:
case 0xb1040003:
// BMSEL はバイトアクセスできる?
putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
return 0;
}
if (addr < 0xb1080000) {
putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
return 0;
}
if (addr < plane0base) {
// 共通ビットマッププレーン
WriteCommonBitmap8(addr, data);
return 0;
}
if (addr < plane7end) {
// ビットマッププレーン
uint32 reladdr = addr - plane0base;
uint plane = reladdr / 0x40000;
uint32 offset = reladdr % 0x40000;
if (plane < nplane) {
// ビットマッププレーンN
putlog(2, "プレーン%d書き込み $%08x <- $%02x", plane, addr, data);
WritePlane8(plane, offset, data);
return 0;
}
// HW 設定によっては存在しないプレーンへの書き込み
putlog(3, "不在プレーン%d書き込み $%08x <- $%02x",
plane, addr, data);
return 0;
}
putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
return 0;
}
uint64
BitmapDevice::Write16(uint32 addr, uint32 data)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
switch (addr) {
case 0xb1000000:
case 0xb1000002:
// このロングワード内へのワードアクセスはロングワードにミラーされる
data = (data << 16) | data;
return Write32(addr & ~3, data);
case 0xb1040000:
case 0xb1040002:
// BMSEL はワードアクセスできる?
putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
return 0;
}
if (addr < 0xb1080000) {
putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
return 0;
}
if (addr < plane0base) {
// 共通ビットマッププレーン
WriteCommonBitmap16(addr, data);
return 0;
}
if (addr < plane7end) {
// ビットマッププレーン
uint32 reladdr = addr - plane0base;
uint plane = reladdr / 0x40000;
uint32 offset = reladdr % 0x40000;
if (plane < nplane) {
// ビットマッププレーンN
putlog(2, "プレーン%d書き込み $%08x <- $%04x", plane, addr, data);
WritePlane16(plane, offset, data);
return 0;
}
// HW 設定によっては存在しないプレーンへの書き込み
putlog(3, "不在プレーン%d書き込み $%08x <- $%04x",
plane, addr, data);
return 0;
}
putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
return 0;
}
uint64
BitmapDevice::Write32(uint32 addr, uint32 data)
{
// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
gMPU->AddCycle(1);
switch (addr) {
case 0xb1000000:
// RFCNT
WriteRFCNT(data);
return 0;
case 0xb1040000:
// BMSEL
bmsel = data & 0xf;
putlog(1, "BMSEL <- $%08x", data);
return 0;
}
if (addr < 0xb1080000) {
putlog(0, "未実装ロング書き込み $%08x <- $%08x", addr, data);
return 0;
}
if (addr < plane0base) {
// 共通ビットマッププレーン
WriteCommonBitmap32(addr, data);
return 0;
}
if (addr < plane7end) {
// ビットマッププレーン
uint32 reladdr = addr - plane0base;
uint plane = reladdr / 0x40000;
uint32 offset = reladdr % 0x40000;
if (plane < nplane) {
// ビットマッププレーンN
putlog(3, "プレーン%d書き込み $%08x <- $%08x", plane, addr, data);
WritePlane32(plane, offset, data);
return 0;
}
// HW 設定によっては存在しないプレーンへの書き込み
putlog(3, "不在プレーン%d書き込み $%08x <- $%08x",
plane, addr, data);
return 0;
}
if (addr < 0xb1300000) {
// 共通ファンクションセット
uint rop = (addr & 0x3f) / 4;
putlog(2, "共通FCSet 書き込み $%08x <- $%08x (%s)",
addr, data, RopStr(rop));
for (int i = 0; i < nplane; i++) {
if ((bmsel & (1 << i))) {
fcs[i] = rop;
mask[i] = data;
}
}
return 0;
}
if (addr < fcset7end) {
// ファンクションセット
int plane = (addr - 0xb1300000) / 0x40000;
if (plane < nplane) {
// ファンクションセットN
uint rop = (addr & 0x3f) / 4;
putlog(2, "FCSet%d書き込み $%08x <- $%08x (%s)",
plane, addr, data, RopStr(rop));
fcs[plane] = rop;
mask[plane] = data;
return 0;
}
// HW 設定によっては存在しない FCSet への書き込み
putlog(3, "不在FCSet%d書き込み $%08x <- $%08x",
plane, addr, data);
return 0;
}
putlog(0, "未実装ロング書き込み $%08x <- $%08x", addr, data);
return 0;
}
uint64
BitmapDevice::Peek8(uint32 addr)
{
if (addr < plane0base) {
// 書き込み専用
return 0xff;
}
if (addr < plane0base + 0x40000 * nplane) {
// プレーンの読み出し
return mem[HLB(addr - plane0base)];
}
// 残りは書き込み専用
return 0xff;
}
void
BitmapDevice::MonitorUpdate(TextScreen& monitor)
{
int i;
monitor.Clear();
// 0 1 2 3 4
// 012345678901234567890123456789012345678901
// RFCNT: $12345678 (X=-123dot, Y=1023dot)
//
// BMSEL=$4 ROP Mask RGB RGB
// Plane#0 Sel THRU $ffffffff $0:RGB $8:RGB
monitor.Print(0, 0, "RFCNT: $%08x (X=%ddot, Y=%ddot)",
rfcnt, hscroll, vscroll);
monitor.Print(3, 2, "BMSEL=$%x ROP Mask", bmsel);
for (i = 0; i < nplane; i++) {
monitor.Print(0, 3 + i, "Plane#%d %s %s $%08x",
i,
(bmsel & (1 << i)) ? "Sel" : "---",
RopStr(fcs[i]), mask[i]);
}
for (; i < MAX_PLANES; i++) {
monitor.Print(0, 3 + i, TA::Disable, "Plane#%d (Not installed)", i);
}
// ついでなのでここで BT454 のパレットも表示。
// 012345
// $0:RGB
const uint8 *pal = gBT454->reg.color;
monitor.Puts(29, 1, "<BT454>");
monitor.Puts(32, 2, "RGB RGB");
for (i = 0; i < 16; i++) {
uint rgb =
((pal[i * 3 + 0] & 0xf0) << 4) +
((pal[i * 3 + 1] & 0xf0) ) +
((pal[i * 3 + 2] & 0xf0) >> 4);
monitor.Print(29 + (i / 8) * 7, (i % 8) + 3, "$%x:%03x", i, rgb);
}
}
// 全画面の更新フラグを立てる
void
BitmapDevice::Invalidate()
{
for (int i = 0; i < 1024; i++) {
atomic_dirty[i] = 1;
}
}
// RFCNT への書き込み
void
BitmapDevice::WriteRFCNT(uint32 data)
{
// レジスタ値
rfcnt = data;
// 水平オフセットを計算
// XXX マニュアルでは -8 まで指定できるように読めるが
// 指定する方法が分からない。
// XXX 193 以上を指定するとどうなるのか (とりあえずほっとく)
// 実機の挙動を目分量で見ると、右にはみ出た部分は
// 256 ラスタ先のラスタデータを描画しているようだ。
hscroll = ((rfcnt >> 16) & 0xff) + 8;
hscroll *= 4;
// ラスタ(垂直)カウンタ
vscroll = (rfcnt + 26) & 0x3ff;
// 全画面更新
Invalidate();
putlog(1, "RFCNT <- $%08x (水平 %ddot, 垂直 %ddot)",
data, hscroll, vscroll);
}
// 共通ビットマッププレーン バイト書き込み
void
BitmapDevice::WriteCommonBitmap8(uint32 addr, uint32 data)
{
uint32 offset = addr - 0xb1080000;
putlog(2, "共通プレーン書き込み $%08x <- $%02x", addr, data);
for (int i = 0; i < nplane; i++) {
if ((bmsel & (1 << i))) {
WritePlane8(i, offset, data);
}
}
}
// 共通ビットマッププレーン ワード書き込み
void
BitmapDevice::WriteCommonBitmap16(uint32 addr, uint32 data)
{
uint32 offset = addr - 0xb1080000;
putlog(2, "共通プレーン書き込み $%08x <- $%04x", addr, data);
for (int i = 0; i < nplane; i++) {
if ((bmsel & (1 << i))) {
WritePlane16(i, offset, data);
}
}
}
// 共通ビットマッププレーン ロング書き込み
void
BitmapDevice::WriteCommonBitmap32(uint32 addr, uint32 data)
{
uint32 offset = addr - 0xb1080000;
putlog(2, "共通プレーン書き込み $%08x <- $%08x", addr, data);
for (int i = 0; i < nplane; i++) {
if ((bmsel & (1 << i))) {
WritePlane32(i, offset, data);
}
}
}
static inline uint32
RasterOp(uint rop, uint32 data, uint32 m)
{
switch (rop) {
case 0: // ZERO
data = 0;
break;
case 1: // AND1
data &= m;
break;
case 2: // AND2
data = ~data & m;
break;
case 3: // 使用不可
// 本当はここにシリアルモード変更コマンドがあるらしい
data = m;
break;
case 4: // AND3
data &= ~m;
break;
case 5: // THROUGH
break;
case 6: // EOR
data ^= m;
break;
case 7: // OR1
data |= m;
break;
case 8: // NOR
data = ~data & ~m;
break;
case 9: // ENOR
data = (data & m) | (~data & ~m);
break;
case 10: // INV1
data = ~data;
break;
case 11: // OR2
data = ~data | m;
break;
case 12: // INV2
data = ~m;
break;
case 13: // OR3
data |= ~m;
break;
case 14: // NAND
data = ~data | ~m;
break;
case 15: // ONE
data = 0xffffffff;
break;
}
return data;
}
// ROP 文字列を返す
const char *
BitmapDevice::RopStr(uint rop)
{
static const char * ropstr[] = {
"ZERO",
"AND1",
"AND2",
"(3)",
"AND3",
"THRU", // THROUGH
"EOR",
"OR1",
"NOR",
"ENOR",
"INV1",
"OR2",
"INV2",
"OR3",
"NAND",
"ONE",
};
return ropstr[rop];
}
// プレーン バイト書き込み
void
BitmapDevice::WritePlane8(uint plane, uint32 offset, uint32 data)
{
uint32 memoffset = plane * 0x40000 + offset;
uint32 m;
uint32 mask8;
m = mem[HLB(memoffset)];
data = RasterOp(fcs[plane], data, m);
mask8 = mask[plane] >> (24 - (memoffset & 3) * 8);
mem[HLB(memoffset)] = (data & mask8) | (m & ~mask8);
atomic_dirty[offset / 256] = 1;
}
// プレーン ワード書き込み
void
BitmapDevice::WritePlane16(uint plane, uint32 offset, uint32 data)
{
uint32 memoffset = plane * 0x40000 + offset;
uint32 m;
uint32 mask16;
m = *(uint16 *)&mem[HLW(memoffset)];
data = RasterOp(fcs[plane], data, m);
if ((memoffset & 2) == 0) {
// 上位ワード
mask16 = mask[plane] >> 16;
} else {
// 下位ワード
mask16 = mask[plane];
}
*(uint16 *)&mem[HLW(memoffset)] = (data & mask16) | (m & ~mask16);
atomic_dirty[offset / 256] = 1;
}
// プレーン ロングワード書き込み
void
BitmapDevice::WritePlane32(uint plane, uint32 offset, uint32 data)
{
uint32 memoffset = plane * 0x40000 + offset;
uint32 m;
m = *(uint32 *)&mem[memoffset];
data = RasterOp(fcs[plane], data, m);
*(uint32 *)&mem[memoffset] = (data & mask[plane]) | (m & ~mask[plane]);
atomic_dirty[offset / 256] = 1;
}
// エミュレータによる画面出力
//
// VM 内からの操作ではなく、エミュレータ本体から画面を操作する
// エミュレーション ROM によるモニタコンソール用の機能。
// サービスを使いたい側 (EmuROM など) が任意の大きさの TextScreen を
// 用意し EmuConsoleOpen() をコールすると (現在のパレットのまま) 画面を
// クリアし、画面中央をこのテキスト VRAM の表示エリアとみなす。
// 以降は EmuConsoleUpdate() で TextScreen の内容に従って画面を更新し、
// 使用後は EmuConsoleClose() で再び画面をクリア (およびリソースの解放) を
// すること。
// エミュレータによる出力開始
void
BitmapDevice::EmuConsoleOpen(TextScreen *t)
{
textscr = t;
screen_w = textscr->GetCol();
screen_h = textscr->GetRow();
prev.resize(screen_w * screen_h);
for (int i = 0; i < screen_w * screen_h; i++) {
prev[i] = 0x0020;
}
// ざっくりセンタリングする
origin_x = (1280 - screen_w * 12) / 2;
origin_y = (1024 - screen_h * 24) / 2;
// ただし横は 48ドット境界から始める
// こうすると1(,2)文字目の24ビットが必ずワード境界から始まる。
origin_x = (origin_x / 48) * 48;
// 同時書き込み設定
bmsel = (1 << nplane) - 1;
// 全画面をクリア
for (uint32 addr = 0xb1080000; addr < 0xb10c0000; addr += 4) {
WriteCommonBitmap32(addr, 0);
}
}
// エミュレータによる出力終了
void
BitmapDevice::EmuConsoleClose()
{
// 全画面をクリア
for (uint32 addr = 0xb1080000; addr < 0xb10c0000; addr += 4) {
WriteCommonBitmap32(addr, 0);
}
// 同時書き込みも元に戻す
bmsel = 0;
textscr = NULL;
prev.clear();
}
// エミュレータによる出力
// ts が出力するテキスト情報、oldts は前回の情報。
void
BitmapDevice::EmuConsoleUpdate()
{
auto& text = textscr->GetBuf();
// 12ドットフォントなので2文字ずつ (24ビットずつ) 更新する
for (int y = 0; y < screen_h; y++) {
for (int x = 0; x < screen_w; x += 2) {
// 2文字分の文字と属性
uint32 t32 = *(const uint32 *)&text[y * screen_w + x];
uint32 p32 = *(const uint32 *)&prev[y * screen_w + x];
if (t32 == p32) {
continue;
}
// ここからは変更があったので出力
*(uint32 *)&prev[y * screen_w + x] = t32;
// 2文字分として直接32ビットキャストして取り出した t32 だと
// エンディアンによって上下が変わるので、ここでは改めて
// 順序通りに2回取り出す
uint32 ch0 = text[y * screen_w + x];
uint32 ch1 = text[y * screen_w + x + 1];
// その文字のフォントデータ上でのオフセット
const uint8 * const fo0 = BuiltinCGROM::Get12x24(ch0 & 0xff);
const uint8 * const fo1 = BuiltinCGROM::Get12x24(ch1 & 0xff);
// 1,2文字目なら true、3,4文字目なら false
bool evenpair = ((x & 2) == 0);
// ビットマップ上の書き始めアドレス
uint32 addr = 0xb1080000 +
((origin_y + y * 24) * 256) +
((origin_x + x * 12) / 8);
for (int i = 0; i < 24; i++) {
// その文字のフォントデータ
uint32 fdat0 = be16toh(*(const uint16*)(fo0 + i * 2));
uint32 fdat1 = be16toh(*(const uint16*)(fo1 + i * 2));
// 属性はとりあえず反転のみサポート
if ((ch0 & TA::On)) {
fdat0 = ~fdat0 & 0xfff0;
}
if ((ch1 & TA::On)) {
fdat1 = ~fdat1 & 0xfff0;
}
// 1文字目と2文字目を組み合わせて 24bit にする
uint32 data = (fdat0 << 8) | (fdat1 >> 4);
// 書き込みは一度に 3バイトずつになるので、
// 効率化のため上位下位ペアで書き込みパターンを変える。
//
// | chr1 | chr2 | chr3 | chr4 |
// +----+----+----+----+----+----+
// | +0 | +1 | +2 | +3 | +4 | +5 |
// +----+----+----+----+----+----+
// |<-.Word->|<.B>|<.B>|<-.Word->|
if (evenpair) {
WriteCommonBitmap16(addr, data >> 8);
WriteCommonBitmap8(addr + 2, data & 0xff);
} else {
WriteCommonBitmap8(addr, data >> 16);
WriteCommonBitmap16(addr + 1, data & 0xffff);
}
addr += 256; // 1ライン進める
}
}
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.