|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// CRTC
//
// e80000..e803ff 内は $40 バイトでミラー。
// e80400..e807ff 内は 256 バイトでミラーになっており、このうち
// +$00..+$7f はバスエラー、
// +$80..+$ff は何かが読める。
// 仕様は $e80480.w に1ポートあるので、これがワードで全域にミラーされている?
// 全体としては この $800 バイトずつが繰り返して見える。
//
// CRTC はデバイスの配置としては BusIO_B 相当で、バイトアクセスも行えるが、
// 内部はバイトレジスタではなくワード単位を基本構造としている。そのため
// BusIO は使わず自力で処理する。ログの観点からもこのほうが都合がいい。
#include "crtc.h"
#include "event.h"
#include "mfp.h"
#include "monitor.h"
#include "scheduler.h"
#include "tvram.h"
#include "videoctlr.h"
// InsideOut p.135
/*static*/ const busdata
CRTCDevice::wait = busdata::Wait(9 * 40_nsec);
// コンストラクタ
CRTCDevice::CRTCDevice()
: inherited(OBJ_CRTC)
{
monitor = gMonitorManager->Regist(ID_MONITOR_CRTC, this);
monitor->SetCallback(&CRTCDevice::MonitorScreen);
monitor->SetSize(79, 27);
}
// デストラクタ
CRTCDevice::~CRTCDevice()
{
}
// 初期化
bool
CRTCDevice::Init()
{
mfp = GetMFPDevice();
tvram = GetTVRAMDevice();
videoctlr = GetVideoCtlrDevice();
auto evman = GetEventManager();
hsync_event = evman->Regist(this,
ToEventCallback(&CRTCDevice::HSyncCallback),
"CRTC H-Sync");
vdisp_event = evman->Regist(this,
ToEventCallback(&CRTCDevice::VDispCallback),
"CRTC V-Disp");
// ラスターコピーは実際には CRTC の機能ではなく TVRAM (VRAM チップ) の
// 機能だが、イベントの時間管理が発生するのは CRTC 側なのでここで管理。
// X68030 搭載の VRAM チップは HM514402A だが、ここでは X68000 XVI 搭載の
// HM53461 の値を使用し、これが最小 190nsec * 2 らしいので
// とりあえず 400nsec としておく。
raster_event = evman->Regist(this,
ToEventCallback(&CRTCDevice::RasterCallback),
"CRTC Raster Copy");
raster_event->time = 400_nsec;
return true;
}
// リセット
void
CRTCDevice::ResetHard(bool poweron)
{
// XXX 初期値適当
crtc.r[20] = 0x0016;
// XXX 適当
// すぐに水平同期と垂直帰線期間を開始する
hsync_state = 1;
hsync_event->time = 0;
scheduler->RestartEvent(hsync_event);
vdisp_state = 1;
vdisp_event->time = 0;
scheduler->RestartEvent(vdisp_event);
}
/*static*/ inline uint32
CRTCDevice::Decoder(uint32 addr)
{
return addr & 0x7ff;
}
busdata
CRTCDevice::Read(busaddr addr)
{
uint32 offset = Decoder(addr.Addr());
busdata data = Peek2(offset);
uint32 reqsize = addr.GetSize();
if (reqsize == 1) {
if ((offset & 1) == 0) {
data >>= 8;
} else {
data &= 0xff;
}
data |= BusData::Size1;
} else {
data |= BusData::Size2;
}
if (__predict_false(loglevel >= 2)) {
if (offset < 0x400) {
// CRTC レジスタは 0x40 バイトでミラー
uint32 regno = (offset >> 1) & 0x1f;
if (regno < countof(crtc.r)) {
// R20, R21 のみ読み出し可能だが、
// R21 へのアクセスは多いのでログレベルを上げておく。
int lv = (regno == 21) ? 3 : 2;
if (reqsize == 1) {
putlog(lv, "R%02u.%c -> $%02x", regno,
(((offset & 1) == 0) ? 'H' : 'L'), data.Data());
} else {
putlog(lv, "R%02u -> $%04x", regno, data.Data());
}
} else {
putlog(3, "$%06x -> $%0*x", addr.Addr(),
reqsize * 2, data.Data());
}
}
}
data |= wait;
return data;
}
busdata
CRTCDevice::Write(busaddr addr, uint32 data)
{
busdata r;
uint32 offset = Decoder(addr.Addr());
uint32 reqsize = addr.GetSize();
uint32 datasize = std::min(2 - (offset & 1), reqsize);
data >>= (reqsize - datasize) * 8;
if (offset < 0x400) {
// CRTC レジスタは 0x40 バイトでミラー
uint32 regno = (offset >> 1) & 0x1f;
if (regno < countof(crtc.r)) {
if (datasize == 1) {
// バイト書き込み
if ((offset & 1) == 0) {
data = (data << 8) | (crtc.r[regno] & 0xff);
} else {
data = (crtc.r[regno] & 0xff00) | data;
}
} else {
// ワード書き込み
}
SetReg(regno, data);
} else {
// ここ何がいるんだ?
putlog(2, "$%06x <- $%0*x", addr.Addr(), datasize * 2, data);
}
} else {
// 動作ポート
if ((offset & 0x80) == 0) { // +$00..+$7f
r.SetBusErr();
} else {
if (datasize == 1) {
// バイト書き込み
if ((offset & 1) == 0) {
data = (data << 8) | crtc.op;
} else {
data = (crtc.op & 0xff00) | data;
}
} else {
// ワード書き込み
}
WriteOp(data);
}
}
r |= wait;
r |= busdata::Size(datasize);
return r;
}
busdata
CRTCDevice::Peek1(uint32 addr)
{
busdata data = Peek2(addr);
if (data.IsOK()) {
if ((addr & 1) == 0) {
return data.Data() >> 8;
} else {
return data.Data() & 0xff;
}
} else {
return BusData::BusErr;
}
}
// addr のワードとしての読み出し値を返す。
// これは内部用で Read() からも呼ばれるので、
// バスエラーなら BusData::BusErr を返す。
busdata
CRTCDevice::Peek2(uint32 addr) const
{
busdata data;
uint32 offset = Decoder(addr);
if (offset < 0x400) {
// CRTC レジスタは 0x40 バイトでミラー
uint32 regno = (offset & 0x3f) / 2;
if (regno == 20 || regno == 21) {
// R20, R21 のみ読み出し可能
data = crtc.r[regno];
} else {
data = 0;
}
} else {
// 動作ポート
if ((offset & 0x80) == 0) {
data.SetBusErr();
} else {
data = crtc.op;
}
}
return data;
}
// 今の所、標準設定しかサポートしていないので、違う時だけログを出したりする。
static const uint16 crtc_supported[] = {
0x0089, // R00
0x000e, // R01
0x001c, // R02
0x007c, // R03
0x0237, // R04
0x0005, // R05
0x0028, // R06
0x0228, // R07
0, // R08
0, // R09
0, // R10
0, // R11
0x0000, // R12
0x0000, // R13
0x0000, // R14
0x0000, // R15
0x0000, // R16
0x0000, // R17
0x0000, // R18
0x0000, // R19
};
void
CRTCDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
int y;
uint16 val;
screen.Clear();
y = 0;
screen.Print(0, y++, "R00:$%04x (V.Total)", crtc.r[0]);
screen.Print(0, y++, "R01:$%04x (V.Sync End)", crtc.r[1]);
screen.Print(0, y++, "R02:$%04x (V.Disp Start)", crtc.r[2]);
screen.Print(0, y++, "R02:$%04x (V.Disp End)", crtc.r[3]);
screen.Print(0, y++, "R04:$%04x (H.Total)", crtc.r[4]);
screen.Print(0, y++, "R05:$%04x (H.Sync End)", crtc.r[5]);
screen.Print(0, y++, "R06:$%04x (H.Disp Start)", crtc.r[6]);
screen.Print(0, y++, "R07:$%04x (H.Disp End)", crtc.r[7]);
screen.Print(0, y++, "R10:$%04x (Text ScrollX)", crtc.r[10]);
screen.Print(0, y++, "R11:$%04x (Text ScrollY)", crtc.r[11]);
screen.Print(0, y++, "R12:$%04x (Grp. Page0 X)", crtc.r[12]);
screen.Print(0, y++, "R13:$%04x (Grp. Page0 Y)", crtc.r[13]);
screen.Print(0, y++, "R14:$%04x (Grp. Page1 X)", crtc.r[14]);
screen.Print(0, y++, "R15:$%04x (Grp. Page1 Y)", crtc.r[15]);
screen.Print(0, y++, "R16:$%04x (Grp. Page2 X)", crtc.r[16]);
screen.Print(0, y++, "R17:$%04x (Grp. Page2 Y)", crtc.r[17]);
screen.Print(0, y++, "R18:$%04x (Grp. Page3 X)", crtc.r[18]);
screen.Print(0, y++, "R19:$%04x (Grp. Page3 Y)", crtc.r[19]);
for (uint i = 0; i < countof(crtc_supported); i++) {
if (8 <= i && i < 12) {
continue;
}
if (crtc.r[i] != crtc_supported[i]) {
screen.Print(29, (i < 8 ? i : i - 2), TA::On,
"(not supported value)");
}
}
// 3 4 5 6 7
// 90123456789012345678901234567890123456789012345678
// SIZ=1024x1024 COL=65536 HF=31kHz VD=undef HD=50MHz
val = crtc.r[20];
screen.Print(0, y, "R20:$%04x (Mode)", val);
screen.Print(29, y, "SIZ=%s", VideoCtlrDevice::siz_str[(val >> 10) & 1]);
screen.Print(43, y, "COL=%s", VideoCtlrDevice::col_str[(val >> 8) & 3]);
screen.Print(53, y, "HF=%ukHz", 15 + (val & 0x10));
static const char * const r20vd_str[] = {
"256",
"512",
"1024",
"undef",
};
screen.Print(62, y, "VD=%s", r20vd_str[(val >> 2) & 3]);
static const char * const r20hd_str[] = {
"256",
"512",
"768",
"50MHz",
};
screen.Print(71, y, "HD=%s", r20hd_str[val & 3]);
y++;
val = crtc.r[21];
screen.Print(0, y, "R21:$%04x (Text Plane)", val);
screen.Print(29, y, "MEN=%c SA=%c AP=%%%c%c%c%c CP=%%%c%c%c%c",
(val & 0x0200) ? '1' : '0',
(val & 0x0100) ? '1' : '0',
(val & 0x0080) ? '1' : '0',
(val & 0x0040) ? '1' : '0',
(val & 0x0020) ? '1' : '0',
(val & 0x0010) ? '1' : '0',
(val & 0x0008) ? '1' : '0',
(val & 0x0004) ? '1' : '0',
(val & 0x0002) ? '1' : '0',
(val & 0x0001) ? '1' : '0');
y++;
val = crtc.r[22];
screen.Print(0, y++, "R22:$%04x (Text Raster Copy) src=$%02x dst=$%02x",
val, (val >> 8), (val & 0xff));
screen.Print(0, y++, "R23:$%04x (Text Access Mask)", crtc.r[23]);
// ビデオコントローラもここに並べたほうが読みやすいか。
y++;
screen.Puts(0, y++, "<Video Controller>");
videoctlr->MonitorScreen(screen, y, crtc.r[20]);
}
// CRTC レジスタへの書き込み。
// reg は CRTC::R00 .. CRTC::R23
void
CRTCDevice::SetReg(uint32 reg, uint32 data)
{
crtc.r[reg] = data;
switch (reg) {
case CRTC::R00 ... CRTC::R07:
case CRTC::R12 ... CRTC::R19:
if (crtc.r[reg] == crtc_supported[reg]) {
putlog(2, "R%02u <- $%04x", reg, data);
return;
}
break;
case CRTC::R10:
putlog(2, "R%02u <- $%04x", reg, data);
tvram->SetScrollX(crtc.r[10]);
return;
case CRTC::R11:
putlog(2, "R%02u <- $%04x", reg, data);
tvram->SetScrollY(crtc.r[11]);
return;
case CRTC::R21:
// 頻度が高いのでログレベルを上げておく
putlog(3, "R%02u <- $%04x", reg, data);
tvram->SetAccessPlane(crtc.r[21]);
return;
case CRTC::R22:
putlog(3, "R%02u <- $%04x", reg, data);
return;
case CRTC::R23:
// 頻度が高いのでログレベルを上げておく
putlog(3, "R%02u <- $%04x", reg, data);
tvram->SetAccessMask(crtc.r[23]);
return;
default:
break;
}
putlog(0, "R%02u <- $%04x (NOT IMPLEMENTED)", reg, data);
}
// 動作ポートへの書き込み。
void
CRTCDevice::WriteOp(uint32 data)
{
uint16 oldop = crtc.op;
crtc.op = data & 0x0b;
putlog(2, "OP <- $%04x", crtc.op);
uint16 change = oldop ^ crtc.op;
if ((change & CRTC::OP_RC)) {
// テキスト画面ラスターコピー
if ((crtc.op & CRTC::OP_RC)) {
// 0->1 開始
raster_copy = true;
putlog(3, "Start Raster copy (ラスターコピー指示");
} else {
// 1->0 は停止じゃなくキャンセル?
raster_copy = false;
putlog(3, "Cancel Raster copy (ラスターコピーキャンセル)");
}
}
if ((change & 0x03)) {
putlog(0, "OP (動作ポート) $%02x <- $%02x (NOT IMPLEMENTED)",
oldop & 0x03, crtc.op & 0x03);
}
}
// 水平同期イベント
void
CRTCDevice::HSyncCallback(Event *ev)
{
switch (hsync_state) {
case 0: // フロントポーチ
// フロントポーチ
ev->time = 2.07_usec;
hsync_state++;
break;
case 1: // 水平同期パルス
// 水平同期期間
ev->time = 3.45_usec;
hsync_state++;
// GPIP の状態を更新
mfp->SetHSync(true);
// ラスターコピーが指示されていたら実行
if (raster_copy) {
raster_copy = false;
uint src = (crtc.r[22] >> 8) * 4;
uint dst = (crtc.r[22] & 0xff) * 4;
tvram->RasterCopy(src, dst);
// 所定時間後に完了させる
scheduler->RestartEvent(raster_event);
}
break;
case 2: // バックポーチ + 表示期間
// バックポーチ + Hdisp
ev->time = 4.14_usec + 22.09_usec;
hsync_state = 0;
// GPIP の状態を更新
mfp->SetHSync(false);
break;
default:
__unreachable();
}
scheduler->StartEvent(ev);
}
// ラスターコピー完了イベント
void
CRTCDevice::RasterCallback(Event *ev)
{
// 終わったら動作ポートの RC ビットを %0 にする?
crtc.op &= ~CRTC::OP_RC;
}
// 垂直表示・帰線イベント
// vdisp_state が 1 なら表示期間開始(V-disp)、0 なら帰線期間開始(V-blank)。
void
CRTCDevice::VDispCallback(Event *ev)
{
// GPIP の状態を更新
mfp->SetVDisp(vdisp_state);
// 今はここで1画面まるごと作成
if (vdisp_state) {
videoctlr->VDisp();
}
// 次のタイミングを計算
// XXX まだ CRTC から計算していない
if (vdisp_state) {
// 垂直表示期間
ev->time = 16250_usec;
vdisp_state = 0;
} else {
// 垂直帰線期間
ev->time = 191_usec + 1111_usec + 476_usec;
vdisp_state = 1;
}
scheduler->StartEvent(ev);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.