--- nono/vm/dmac.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/vm/dmac.cpp 2026/04/29 17:06:00 1.1.1.19 @@ -1,284 +1,523 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt +// + +// +// DMAC (HD63450) // #include "dmac.h" +#include "adpcm.h" +#include "event.h" +#include "fdc.h" +#include "interrupt.h" +#include "mainbus.h" +#include "monitor.h" +#include "mpu.h" +#include "scheduler.h" +#include "syncer.h" + +// time 時間後に呼び出すコールバックをセットする。 +// func の書式が面倒なのを省略して書きたいため。 +#define CallAfter(func_, time_, wait_) do { \ + event->SetCallback(&DMACDevice::func_); \ + event->time = time_ * 80_nsec + wait_; \ + scheduler->StartEvent(event); \ +} while (0) + +// InsideOut p.135 +static const busdata read_wait = busdata::Wait(37 * 40_nsec); +static const busdata write_wait = busdata::Wait(31 * 40_nsec); + +// チャンネルログ +#define chan_putlog(chan, lv, fmt...) do { \ + if ((chan)->loglevel >= (lv)) \ + (chan)->putlogn(fmt); \ +} while (0) +// コンストラクタ DMACDevice::DMACDevice() + : inherited(OBJ_DMAC) { - logname = "dmac"; - devname = "DMAC"; - devaddr = baseaddr; - devlen = 0x2000; + channels[0].reset(new DMACChan(0, "FD")); + channels[1].reset(new DMACChan(1, "HD")); + channels[2].reset(new DMACChan(2, "User")); + channels[3].reset(new DMACChan(3, "ADPCM")); + + // PCL 信号線 + // #0 はスーパーインポーズしてなければ常に Low + channels[0]->pcl_pin = false; + channels[0]->pcl_prev = false; + // #1 はプルアップされているので常に High + channels[1]->pcl_pin = true; + channels[1]->pcl_prev = true; + // #2 は外部スロット用だが未接続なので High + channels[2]->pcl_pin = true; + channels[2]->pcl_prev = true; + // #3 は未対応 + + monitor = gMonitorManager->Regist(ID_MONITOR_DMAC, this); + monitor->SetCallback(&DMACDevice::MonitorScreen); + monitor->SetSize(80, 34); } +// デストラクタ DMACDevice::~DMACDevice() { } -uint64 -DMACDevice::Read8(uint32 addr) +// 初期化 +bool +DMACDevice::Init() { - uint8 data; - int ch; + adpcm = GetADPCMDevice(); + fdc = GetFDCDevice(); + interrupt = GetInterruptDevice(); + mainbus = GetMainbusDevice(); + syncer = GetSyncer(); + + auto evman = GetEventManager(); + event = evman->Regist(this, NULL, "DMAC"); + + return true; +} + +// リセット +void +DMACDevice::ResetHard(bool poweron) +{ + putlog(2, "Reset"); + + // Clears GCR, DCR, OCR, SCR, CCR, CSR, CPR and CER for all channels. + // NIV and EIV are all set to $0F (uninitialized interrupt vector). + // MTC, MAR, DAR, BTC, BAR, MFC, DFC and BFC are not affected. + + // 電源オン時、少なくとも BFC は $07 のようだ + // (X68030 IPLROM が #0, #1 の BFC を初期化していないので読める)。 + // MFC、DFC くらいは同様かも知れない。 + if (poweron) { + for (auto& chan : channels) { + chan->SetMFC(0x07); + chan->SetDFC(0x07); + chan->SetBFC(0x07); + } + } + + gcr = 0; + for (auto& chan : channels) { + chan->SetDCR(0); + chan->SetOCR(0); + chan->SetSCR(0); + chan->SetCCR(0); + chan->csr = 0; + chan->active = false; + chan->cpr = 0; + chan->cer = 0; + + chan->niv = 0x0f; + chan->eiv = 0x0f; + + chan->priority = 0; + } + syncer->NotifyDMACActive(false); + ChangeInterrupt(); + + // イベントを停止 + event->SetName("DMAC"); + scheduler->StopEvent(event); +} + +busdata +DMACDevice::Read(busaddr addr) +{ + uint32 paddr = addr.Addr(); + uint32 reqsize = addr.GetSize(); + uint32 datasize = std::min(2 - (paddr & 1U), reqsize); + busdata data; + + if (datasize == 1) { + data = Peek1(paddr); + } else { + data = Peek1(paddr) << 8; + data |= Peek1(paddr + 1); + } + + if (__predict_false(loglevel >= 3)) { + uint32 offset = paddr & 0xff; + uint ch = offset / 0x40; + uint n = offset % 0x40; + DMACChan *chan = channels[ch].get(); + if (datasize == 1) { + if (regname1[n]) { + chan->putlogn("%s -> $%02x", regname1[n], data.Data()); + } else if (offset == 0xff) { + putlogn("GCR -> $%02x", data.Data()); + } + } else { + if (regname2[n / 2]) { + chan->putlogn("%s -> $%04x", regname2[n / 2], data.Data()); + } else if (offset == 0xfe) { + putlogn("GCR(W) -> $%04x", data.Data()); + } + } + } + + data |= read_wait; + data |= busdata::Size(datasize); + return data; +} + +busdata +DMACDevice::Write(busaddr addr, uint32 data) +{ + uint32 paddr = addr.Addr(); + uint32 reqsize = addr.GetSize(); + uint32 datasize = std::min(2 - (paddr & 1U), reqsize); + data >>= (reqsize - datasize) * 8; + + uint32 offset = paddr & 0xff; + uint32 ch = offset / 0x40; + uint32 n = offset % 0x40; + DMACChan *chan = channels[ch].get(); + if (datasize == 1) { + WriteByte(chan, n, data); + } else { + WriteWord(chan, n, data); + } + + busdata r = write_wait; + r |= busdata::Size(datasize); + return r; +} + +// STR か ACT が立っている時に書き込むとタイミングエラー +#define TIMING_ERR_IF_RUNNING do { \ + if (chan->str || chan->active) { \ + Error(chan, DMAC::CER_TIMING); \ + break; \ + } \ +} while (0) + +// ログ表示版 +#define TIMING_ERR_IF_RUNNING_log(regname) do { \ + if (chan->str || chan->active) { \ + chan_putlog(chan, 2, "%s <- $%02x", regname, data); \ + Error(chan, DMAC::CER_TIMING); \ + break; \ + } \ +} while (0) + +// バイト書き込み。 +busdata +DMACDevice::WriteByte(DMACChan *chan, uint32 n, uint32 data) +{ + uint ch = chan->ch; - ch = (addr - baseaddr) / 0x40; - struct DMAC::channel *chan = &dmac.chan[ch]; - int n = (addr - baseaddr) % 0x40; switch (n) { case DMAC::CSR: - data = chan->csr; + WriteCSR(chan, data); break; case DMAC::CER: - data = chan->cer; + // Read only break; case DMAC::DCR: - data = chan->dcr; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + TIMING_ERR_IF_RUNNING; + chan->SetDCR(data); break; case DMAC::OCR: - data = chan->ocr; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + TIMING_ERR_IF_RUNNING; + chan->SetOCR(data); break; case DMAC::SCR: - data = chan->scr; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + TIMING_ERR_IF_RUNNING; + chan->SetSCR(data); break; case DMAC::CCR: - data = chan->ccr; + { + // %0 -> %1 に変化したビットだけを評価する。 + uint8 up = (chan->GetCCR() ^ data) & data; + // バイトサイズのログを (up 込みで) 表示。 + chan_putlog(chan, 2, "CCR <- $%02x (up=$%02x)", data, up); + // CCR を更新してから WriteCCR() で動作をする。 + chan->SetCCR(data); + WriteCCR(chan, data, up); break; + } case DMAC::MTC: - data = chan->mtc >> 8; + TIMING_ERR_IF_RUNNING_log("MTC:H"); + chan->mtc = (chan->mtc & 0x00ff) | (data << 8); + chan_putlog(chan, 2, "MTC:H <- $%02x (MTC=$%04x)", data, chan->mtc); break; case DMAC::MTC + 1: - data = chan->mtc & 0xff; + TIMING_ERR_IF_RUNNING_log("MTC:L"); + chan->mtc = (chan->mtc & 0xff00) | data; + chan_putlog(chan, 2, "MTC:L <- $%02x (MTC=$%04x)", data, chan->mtc); break; case DMAC::MAR: - data = chan->mar >> 24; + TIMING_ERR_IF_RUNNING_log("MAR:0"); + chan->mar = (chan->mar & 0x00ffffff) | (data << 24); + chan_putlog(chan, 2, "MAR:0 <- $%02x (MAR=$%08x)", data, chan->mar); break; case DMAC::MAR + 1: - data = (chan->mar >> 16) & 0xff; + TIMING_ERR_IF_RUNNING_log("MAR:1"); + chan->mar = (chan->mar & 0xff00ffff) | (data << 16); + chan_putlog(chan, 2, "MAR:1 <- $%02x (MAR=$%08x)", data, chan->mar); break; case DMAC::MAR + 2: - data = (chan->mar >> 8) & 0xff; + TIMING_ERR_IF_RUNNING_log("MAR:2"); + chan->mar = (chan->mar & 0xffff00ff) | (data << 8); + chan_putlog(chan, 2, "MAR:2 <- $%02x (MAR=$%08x)", data, chan->mar); break; case DMAC::MAR + 3: - data = chan->mar & 0xff; + TIMING_ERR_IF_RUNNING_log("MAR:3"); + chan->mar = (chan->mar & 0xffffff00) | data; + chan_putlog(chan, 2, "MAR:3 <- $%02x (MAR=$%08x)", data, chan->mar); break; case DMAC::DAR: - data = chan->dar >> 24; + TIMING_ERR_IF_RUNNING_log("DAR:0"); + chan->dar = (chan->dar & 0x00ffffff) | (data << 24); + chan_putlog(chan, 2, "DAR:0 <- $%02x (DAR=$%08x)", data, chan->dar); break; case DMAC::DAR + 1: - data = (chan->dar >> 16) & 0xff; + TIMING_ERR_IF_RUNNING_log("DAR:1"); + chan->dar = (chan->dar & 0xff00ffff) | (data << 16); + chan_putlog(chan, 2, "DAR:1 <- $%02x (DAR=$%08x)", data, chan->dar); break; case DMAC::DAR + 2: - data = (chan->dar >> 8) & 0xff; + TIMING_ERR_IF_RUNNING_log("DAR:2"); + chan->dar = (chan->dar & 0xffff00ff) | (data << 8); + chan_putlog(chan, 2, "DAR:2 <- $%02x (DAR=$%08x)", data, chan->dar); break; case DMAC::DAR + 3: - data = chan->dar & 0xff; + TIMING_ERR_IF_RUNNING_log("DAR:3"); + chan->dar = (chan->dar & 0xffffff00) | data; + chan_putlog(chan, 2, "DAR:3 <- $%02x (DAR=$%08x)", data, chan->dar); break; case DMAC::BTC: - data = chan->btc >> 8; + chan->btc = (chan->btc & 0x00ff) | (data << 8); + chan_putlog(chan, 2, "BTC:H <- $%02x (BTC=$%04x)", data, chan->btc); break; case DMAC::BTC + 1: - data = chan->btc & 0xff; + chan->btc = (chan->btc & 0xff00) | data; + chan_putlog(chan, 2, "BTC:L <- $%02x (BTC=$%04x)", data, chan->btc); break; case DMAC::BAR: - data = chan->bar >> 24; + chan->bar = (chan->bar & 0x00ffffff) | (data << 24); + chan_putlog(chan, 2, "BAR:0 <- $%02x (BAR=$%08x)", data, chan->bar); break; case DMAC::BAR + 1: - data = (chan->bar >> 16) & 0xff; + chan->bar = (chan->bar & 0xff00ffff) | (data << 16); + chan_putlog(chan, 2, "BAR:1 <- $%02x (BAR=$%08x)", data, chan->bar); break; case DMAC::BAR + 2: - data = (chan->bar >> 8) & 0xff; + chan->bar = (chan->bar & 0xffff00ff) | (data << 8); + chan_putlog(chan, 2, "BAR:2 <- $%02x (BAR=$%08x)", data, chan->bar); break; case DMAC::BAR + 3: - data = chan->bar & 0xff; + chan->bar = (chan->bar & 0xffffff00) | data; + chan_putlog(chan, 2, "BAR:3 <- $%02x (BAR=$%08x)", data, chan->bar); break; case DMAC::NIV: - data = chan->niv; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + chan->SetNIV(data); break; case DMAC::EIV: - data = chan->eiv; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + chan->SetEIV(data); break; case DMAC::MFC: - data = chan->mfc; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + TIMING_ERR_IF_RUNNING; + chan->SetMFC(data); break; case DMAC::CPR: - data = chan->cpr; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + chan->SetCPR(data); break; case DMAC::DFC: - data = chan->dfc; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + TIMING_ERR_IF_RUNNING; + chan->SetDFC(data); break; case DMAC::BFC: - data = chan->bfc; + chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data); + chan->SetBFC(data); break; case DMAC::GCR: if (ch == 3) { - data = dmac.gcr; - break; - } else { - data = 0xff; + WriteGCR(data); } break; default: - data = 0xff; break; } - return data; + return write_wait; } -uint64 -DMACDevice::Write8(uint32 addr, uint32 data) +// ワード書き込み。 +busdata +DMACDevice::WriteWord(DMACChan *chan, uint32 n, uint32 data) { - int ch; + uint ch = chan->ch; - ch = (addr - baseaddr) / 0x40; - struct DMAC::channel *chan = &dmac.chan[ch]; - int n = (addr - baseaddr) % 0x40; switch (n) { - case DMAC::CSR: - write_csr(ch, data); - break; - - case DMAC::CER: - // Read only - break; - - case DMAC::DCR: - chan->dcr = data & 0xfb; - break; - - case DMAC::OCR: - chan->ocr = data; - break; - - case DMAC::SCR: - chan->scr = data & 0x0f; - break; - - case DMAC::CCR: - write_ccr(ch, data); + case DMAC::CSR: // CSR | CER + // XXX ログがバイトサイズのまま出るがどうするか + WriteCSR(chan, data >> 8); + // CER は Read Only + break; + + case DMAC::DCR: // DCR | OCR + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + TIMING_ERR_IF_RUNNING; + chan->SetDCR(data >> 8); + chan->SetOCR(data & 0xff); + break; + + case DMAC::SCR: // SCR | CCR + { + // CCR の %0 -> %1 に変化したビットだけを取り出す。 + uint8 up = (chan->GetCCR() ^ data) & data; + // ワードサイズのログを (CCR の up 込みで) 表示。 + chan_putlog(chan, 2, "SCR:CCR <- $%04x (up=$%02x)", data, up); + // 両方のレジスタの値を更新。 + chan->SetSCR(data >> 8); + chan->SetCCR(data); + // ワード書き込みで STR が立っているとタイミングエラー (p.17) は + // ここで評価する。SCR 更新の後。 + TIMING_ERR_IF_RUNNING; + // タイミングエラーでなければ CCR に基づいて動作。 + WriteCCR(chan, data, up); break; + } case DMAC::MTC: - chan->mtc = (chan->mtc & 0x00ff) | (data << 8); - break; - case DMAC::MTC + 1: - chan->mtc = (chan->mtc & 0xff00) | data; + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + TIMING_ERR_IF_RUNNING; + chan->mtc = data; break; case DMAC::MAR: - chan->mar = (chan->mar & 0x00ffffff) | (data << 24); - break; - case DMAC::MAR + 1: - chan->mar = (chan->mar & 0xff00ffff) | (data << 16); + TIMING_ERR_IF_RUNNING_log("MAR:H"); + chan->mar = (chan->mar & 0x0000ffff) | (data << 16); + chan_putlog(chan, 2, "MAR:H <- $%04x (MAR=$%08x)", data, chan->mar); break; case DMAC::MAR + 2: - chan->mar = (chan->mar & 0xffff00ff) | (data << 8); - break; - case DMAC::MAR + 3: - chan->mar = (chan->mar & 0xffffff00) | data; + TIMING_ERR_IF_RUNNING_log("MAR:L"); + chan->mar = (chan->mar & 0xffff0000) | data; + chan_putlog(chan, 2, "MAR:L <- $%04x (MAR=$%08x)", data, chan->mar); break; case DMAC::DAR: - chan->dar = (chan->dar & 0x00ffffff) | (data << 24); - break; - case DMAC::DAR + 1: - chan->dar = (chan->dar & 0xff00ffff) | (data << 16); + TIMING_ERR_IF_RUNNING_log("DAR:H"); + chan->dar = (chan->dar & 0x0000ffff) | (data << 16); + chan_putlog(chan, 2, "DAR:H <- $%04x (DAR=$%08x)", data, chan->dar); break; case DMAC::DAR + 2: - chan->dar = (chan->dar & 0xffff00ff) | (data << 8); - break; - case DMAC::DAR + 3: - chan->dar = (chan->dar & 0xffffff00) | data; + TIMING_ERR_IF_RUNNING_log("DAR:L"); + chan->dar = (chan->dar & 0xffff0000) | data; + chan_putlog(chan, 2, "DAR:L <- $%04x (DAR=$%08x)", data, chan->dar); break; case DMAC::BTC: - chan->btc = (chan->btc & 0x00ff) | (data << 8); - break; - case DMAC::BTC + 1: - chan->btc = (chan->btc & 0xff00) | data; + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + TIMING_ERR_IF_RUNNING; + chan->btc = data; break; case DMAC::BAR: - chan->bar = (chan->bar & 0x00ffffff) | (data << 24); - break; - case DMAC::BAR + 1: - chan->bar = (chan->bar & 0xff00ffff) | (data << 16); + TIMING_ERR_IF_RUNNING_log("BAR:H"); + chan->bar = (chan->bar & 0x0000ffff) | (data << 16); + chan_putlog(chan, 2, "BAR:H <- $%04x (BAR=$%08x)", data, chan->bar); break; case DMAC::BAR + 2: - chan->bar = (chan->bar & 0xffff00ff) | (data << 8); - break; - case DMAC::BAR + 3: - chan->bar = (chan->bar & 0xffffff00) | data; + TIMING_ERR_IF_RUNNING_log("BAR:L"); + chan->bar = (chan->bar & 0xffff0000) | data; + chan_putlog(chan, 2, "BAR:L <- $%04x (BAR=$%08x)", data, chan->bar); break; - case DMAC::NIV: - chan->niv = data; + case DMAC::NIV & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + chan->SetNIV(data); break; - case DMAC::EIV: - chan->eiv = data; + case DMAC::EIV & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + chan->SetEIV(data); break; - case DMAC::MFC: - chan->mfc = data & 7; + case DMAC::MFC & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + TIMING_ERR_IF_RUNNING; + chan->SetMFC(data); break; - case DMAC::CPR: - chan->cpr = data & 3; + case DMAC::CPR & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + chan->SetCPR(data); break; - case DMAC::DFC: - chan->dfc = data & 7; + case DMAC::DFC & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + TIMING_ERR_IF_RUNNING; + chan->SetDFC(data); break; - case DMAC::BFC: - chan->bfc = data & 7; + case DMAC::BFC & ~1U: + chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data); + chan->SetBFC(data); break; - case DMAC::GCR: + case DMAC::GCR & ~1U: if (ch == 3) { - dmac.gcr = data; + WriteGCR(data); } break; default: break; } - return 0; + + return write_wait; } -uint64 -DMACDevice::Peek8(uint32 addr) +busdata +DMACDevice::Peek1(uint32 addr) { uint8 data; - int ch; - ch = (addr - baseaddr) / 0x40; - struct DMAC::channel *chan = &dmac.chan[ch]; - int n = (addr - baseaddr) % 0x40; + uint32 offset = addr & 0xff; + uint ch = offset / 0x40; + uint n = offset % 0x40; + DMACChan *chan = channels[ch].get(); + switch (n) { case DMAC::CSR: - data = chan->csr; + data = chan->GetCSR(); break; case DMAC::CER: @@ -286,19 +525,19 @@ DMACDevice::Peek8(uint32 addr) break; case DMAC::DCR: - data = chan->dcr; + data = chan->GetDCR(); break; case DMAC::OCR: - data = chan->ocr; + data = chan->GetOCR(); break; case DMAC::SCR: - data = chan->scr; + data = chan->GetSCR(); break; case DMAC::CCR: - data = chan->ccr; + data = chan->GetCCR(); break; case DMAC::MTC: @@ -363,7 +602,7 @@ DMACDevice::Peek8(uint32 addr) break; case DMAC::MFC: - data = chan->mfc; + data = chan->mfc.GetFC(); break; case DMAC::CPR: @@ -371,17 +610,16 @@ DMACDevice::Peek8(uint32 addr) break; case DMAC::DFC: - data = chan->dfc; + data = chan->dfc.GetFC(); break; case DMAC::BFC: - data = chan->bfc; + data = chan->bfc.GetFC(); break; case DMAC::GCR: if (ch == 3) { - data = dmac.gcr; - break; + data = gcr; } else { data = 0xff; } @@ -396,14 +634,1662 @@ DMACDevice::Peek8(uint32 addr) } void -DMACDevice::write_csr(int ch, uint32 data) +DMACDevice::MonitorScreen(Monitor *, TextScreen& screen) { - putlog(1, "write_csr[%d] <- $%02x 未実装書き込み", ch, data); + int x; + int y; + + screen.Clear(); + + y = 1; + screen.Puts(0, y++, "BaseAddress"); + screen.Print(0, y++, "+$%02x CSR:", DMAC::CSR); + y += 2; + screen.Print(0, y++, "+$%02x CER:", DMAC::CER); + screen.Print(0, y++, "+$%02x DCR:", DMAC::DCR); + screen.Print(5, y++, "%6s", ".XRM"); + screen.Print(5, y++, "%6s", ".DTYP"); + screen.Print(5, y++, "%6s", ".DPS"); + screen.Print(5, y++, "%6s", ".PCL"); + screen.Print(0, y++, "+$%02x OCR:", DMAC::OCR); + screen.Print(5, y++, "%6s", ".DIR"); + screen.Print(5, y++, "%6s", ".SIZE"); + screen.Print(5, y++, "%6s", ".CHAIN"); + screen.Print(5, y++, "%6s", ".REQG"); + screen.Print(0, y++, "+$%02x SCR:", DMAC::SCR); + screen.Print(5, y++, "%6s", ".MAC"); + screen.Print(5, y++, "%6s", ".DAC"); + screen.Print(0, y++, "+$%02x CCR:", DMAC::CCR); + y += 2; + screen.Print(0, y++, "+$%02x MTC:", DMAC::MTC); + screen.Print(0, y++, "+$%02x MAR:", DMAC::MAR); + screen.Print(0, y++, "+$%02x DAR:", DMAC::DAR); + screen.Print(0, y++, "+$%02x BTC:", DMAC::BTC); + screen.Print(0, y++, "+$%02x BAR:", DMAC::BAR); + screen.Print(0, y++, "+$%02x NIV:", DMAC::NIV); + screen.Print(0, y++, "+$%02x EIV:", DMAC::EIV); + screen.Print(0, y++, "+$%02x MFC:", DMAC::MFC); + screen.Print(0, y++, "+$%02x CPR:", DMAC::CPR); + screen.Print(0, y++, "+$%02x DFC:", DMAC::DFC); + screen.Print(0, y++, "+$%02x BFC:", DMAC::BFC); + + for (auto& chan : channels) { + uint val; + int ch = chan->ch; + x = 13 + ch * 17; + y = 0; + + // 地味だけど有効なチャンネルをハイライトしてみる + screen.Print(x, y++, (chan->active ? TA::Em : TA::Normal), + "#%u (%s)", chan->ch, chan->desc); + + // アドレス + screen.Print(x, y++, "$%06x", baseaddr + ch * 0x40); + + // CSR + val = chan->GetCSR(); + screen.Print(x, y++, "$%02x", val); + static const char * const csrname[] = { + "COC", "BTC", "NDT", "ERR", "ACT", "DIT", "PCT", "PCS", + }; + MonitorReg4(screen, x, y++, val >> 4, &csrname[0]); + MonitorReg4(screen, x, y++, val & 0xff, &csrname[4]); + + // CER + val = chan->cer; + const char *e; + if (val <= 0x11 && errnames[val]) { + e = errnames[val]; + } else { + e = "?"; + } + screen.Print(x, y, "$%02x", chan->cer); + if (val != 0) { + screen.Print(x + 3, y, ":%s", e); + } + y++; + + // DCR + screen.Print(x, y++, "$%02x", chan->GetDCR()); + + // DCR:XRM + static const char * const dcr_xrm[] = { + //1234567890123 + "Burst", + "undefined", + "CycleW/O Hold", + "CycleWithHold", + }; + val = chan->GetXRM(); + screen.Print(x, y++, "%u:%s", val, dcr_xrm[val]); + + // DCR:DTYP + static const char * const dcr_dtyp[] = { + //1234567890123 + "68000", + "6800", + "ACK", + "ACK+READY", + }; + val = chan->GetDTYP(); + screen.Print(x, y++, "%u:%s", val, dcr_dtyp[val]); + + // DCR:DPS + static const char * const dcr_dps[] = { + //1234567890123 + "8bit", + "16bit", + }; + val = chan->GetDPS(); + screen.Print(x, y++, "%u:%s", val, dcr_dps[val]); + + // DCR:PCL + static const char * const dcr_pcl_name[] = { + //1234567890123 + "Status", + "StatusInt", + "Pulse(output)", + "Abort", + }; + val = chan->dcr_pcl; + screen.Print(x, y++, "%u:%s", val, dcr_pcl_name[val]); + + // OCR + screen.Print(x, y++, "$%02x", chan->GetOCR()); + + // OCR:DIR + static const char * const ocr_dir[] = { + //1234567890123 + "MemoryToDevice", + "DeviceToMemory", + }; + val = chan->GetDIR(); + screen.Print(x, y++, "%u:%s", val, ocr_dir[val]); + + // OCR:SIZE + static const char * const ocr_size[] = { + //1234567890123 + "8bit (Packed)", + "16bit", + "32bit", + "8bit Unpacked", + }; + val = chan->GetSIZE(); + screen.Print(x, y++, "%u:%s", val, ocr_size[val]); + + // OCR:CHAIN + static const char * const ocr_chain[] = { + //1234567890123 + "NoChain", + "undefined", + "ArrayChain", + "LinkArrayChain", + }; + val = chan->GetCHAIN(); + screen.Print(x, y++, "%u:%s", val, ocr_chain[val]); + + // OCR:REQG + static const char * const ocr_reqg[] = { + //1234567890123 + "AutoReq(Limit)", + "AutoReq(Max)", + "ExternalReq", + "AutoThenExt", + }; + val = chan->GetREQG(); + screen.Print(x, y++, "%u:%s", val, ocr_reqg[val]); + + // SCR + screen.Print(x, y++, "$%02x", chan->GetSCR()); + + // SCR:MAC,DAC + static const char * const scr_xac[] = { + //1234567890123 + "NoCount", + "CountUp", + "CountDown", + "undefined", + }; + val = chan->GetMAC(); + screen.Print(x, y++, "%u:%s", val, scr_xac[val]); + val = chan->GetDAC(); + screen.Print(x, y++, "%u:%s", val, scr_xac[val]); + + // CCR + val = chan->GetCCR(); + screen.Print(x, y++, "$%02x", val); + static const char * const ccrnames[] = { + "STR", "CNT", "HLT", "SAB", "INT", "-", "-", "-", + }; + MonitorReg4(screen, x, y++, (val >> 4), &ccrnames[0]); + MonitorReg4(screen, x, y++, (val & 0xff), &ccrnames[4]); + + // MTC + screen.Print(x, y++, "$%04x", chan->mtc); + // MAR + // X680x0 がターゲットなので24bit超える設定は目立たせる + if (chan->mar > 0xffffff) { + screen.Print(x, y++, TA::On, "$%08x", chan->mar); + } else { + screen.Print(x, y++, "$%06x", chan->mar); + } + // DAR + if (chan->dar > 0xffffff) { + screen.Print(x, y++, TA::On, "$%08x", chan->dar); + } else { + screen.Print(x, y++, "$%06x", chan->dar); + } + // BTC + screen.Print(x, y++, "$%04x", chan->btc); + // BAR + if (chan->bar > 0xffffff) { + screen.Print(x, y++, TA::On, "$%08x", chan->bar); + } else { + screen.Print(x, y++, "$%06x", chan->bar); + } + + // NIV + screen.Print(x, y++, "$%02x", chan->niv); + // EIV + screen.Print(x, y++, "$%02x", chan->eiv); + + // MFC + screen.Print(x, y++, "$%02x", chan->mfc.GetFC()); + // CPR + screen.Print(x, y++, "$%02x", chan->cpr); + // DFC + screen.Print(x, y++, "$%02x", chan->dfc.GetFC()); + // BFC + screen.Print(x, y++, "$%02x", chan->bfc.GetFC()); + + if (ch == 3) { + screen.Print(x - 10, y++, "+$3f GCR: $%02x", gcr); + } + } +} + +// 4ビット分を表示する。MonitorScreen の下請け +void +DMACDevice::MonitorReg4(TextScreen& screen, + int x, int y, uint32 reg, const char * const *names) +{ + for (uint i = 0; i < 4; i++) { + if (names[i][0] == '-') { + screen.Puts(x + i * 4, y, TA::Disable, "---"); + } else { + bool b = reg & (1U << (3 - i)); + screen.Puts(x + i * 4, y, TA::OnOff(b), names[i]); + } + } +} + +/*static*/ const char * const +DMACDevice::errnames[] = { + //12345678901 + "", // $00 No Error + "ConfigErr", // $01 + "OperTiming", // $02 + NULL, // $03 + NULL, // $04 + "AddrErrInMAR", // $05 + "AddrErrInDAR", // $06 + "AddrErrInBAR", // $07 + NULL, // $08 + "BusErrInMAR", // $09 + "BusErrInDAR", // $0a + "BusErrInBAR", // $0b + NULL, // $0c + "CntErrInMTC", // $0d + NULL, // $0e + "CntErrInBTC", // $0f + "ExternAbort", // $10 + "SoftAbort", // $11 +}; + +// CSR への書き込み。 +void +DMACDevice::WriteCSR(DMACChan *chan, uint32 data) +{ + // 上位3ビットと DIT は %1 の書き込みでクリア + chan->csr &= ~(data & 0xe4); + + // ERR も %1 の書き込みでクリア。 + // このとき CER もクリアする。 + if ((data & DMAC::CSR_ERR)) { + chan->csr &= ~DMAC::CSR_ERR; + chan->cer = 0; + } + + // PCT も %1 の書き込みでクリア。 + // PCT は PCL が High → Low の時セットなので pcl_prev を下げればよい + if ((data & DMAC::CSR_PCT)) { + chan->pcl_prev = false; + } + + chan_putlog(chan, 2, "CSR <- $%02x (CSR = $%02x)", data, chan->GetCSR()); + + ChangeInterrupt(); +} + +// GCR への書き込み。 +void +DMACDevice::WriteGCR(uint8 data) +{ + putlog(2, "GCR <- $%02x", data); + gcr = data & 0x0f; } +// CCR への書き込み。これだけいろいろ変則的なので注意。 +// up は data のうち %0 -> %1 に変化したビット。 +// SetCCR() 実行済み、ログは出力済み。 void -DMACDevice::write_ccr(int ch, uint32 data) +DMACDevice::WriteCCR(DMACChan *chan, uint32 data, uint8 up) { - putlog(1, "write_ccr[%d] <- $%02x 未実装書き込み", ch, data); + // コンフィギュレーションエラーのチェック。 + // データシート p43 Error Conditions (a) + if ((up & DMAC::CCR_STR)) { + // (i) The CNT bit is set at the same time STR bit in the chaining mode. + if ((up & DMAC::CCR_CNT) && chan->IsChain()) { + Error(chan, DMAC::CER_CONFIG); + return; + } + + if (chan->IsSingleAddress()) { + // (ii) DTYP specifies a single addressing mode, and + // the device port size is not the same as the operand size. + if (chan->GetDPS() == DMAC::DPS_8BIT) { + if (!(chan->GetSIZE() == DMAC::SIZE_8BIT_PACK || + chan->GetSIZE() == DMAC::SIZE_8BIT_UNPK)) + { + Error(chan, DMAC::CER_CONFIG); + return; + } + } else { + if (!(chan->GetSIZE() == DMAC::SIZE_16BIT)) { + Error(chan, DMAC::CER_CONFIG); + return; + } + } + } else { + // (iii) DTYP specifies a dual addressing mode, DPS is 16 bits, + // SIZE is 8 bits and REQG is "10" or "11". + if (chan->GetDPS() == DMAC::DPS_16BIT) { + // XXX UNPK は? + if (chan->GetSIZE() == DMAC::SIZE_8BIT_PACK) { + if (chan->GetREQG() == DMAC::REQG_EXTERNAL || + chan->GetREQG() == DMAC::REQG_AUTOFIRST) + { + Error(chan, DMAC::CER_CONFIG); + return; + } + } + } + } + + // (iv) An undefined configuration is set in the registers. + if (chan->GetXRM() == 1 || + chan->GetMAC() == 3 || + chan->GetDAC() == 3 || + chan->GetCHAIN() == DMAC::CHAIN_reserved) + { + Error(chan, DMAC::CER_CONFIG); + return; + } + if (chan->IsSingleAddress() == false && + chan->GetSIZE() == DMAC::SIZE_8BIT_UNPK && + chan->GetDPS() != DMAC::DPS_8BIT) + { + Error(chan, DMAC::CER_CONFIG); + return; + } + + // (e) Count Error + // ここでは (i), (ii) のみ + if (chan->IsChain() == false && chan->mtc == 0) { + Error(chan, DMAC::CER_COUNT_MTC); + return; + } + if (chan->GetCHAIN() == DMAC::CHAIN_ARRAY && chan->btc == 0) { + Error(chan, DMAC::CER_COUNT_BTC); + return; + } + } + + // SAB (Software Abort) + if ((up & DMAC::CCR_SAB)) { + AbortTransfer(chan); + } + + // HLT (Halt Operation) + if ((up & DMAC::CCR_HLT)) { + putlog(0, "CCR HLT (NOT IMPLEMENTED)"); + } + + // CNT (Continue Operation) + if ((up & DMAC::CCR_CNT)) { + // Opeation Timing Error (i) + if (chan->IsChain() && chan->active) { + Error(chan, DMAC::CER_TIMING); + return; + } + if (chan->str == false && chan->active == false) { + Error(chan, DMAC::CER_TIMING); + return; + } + putlog(0, "CCR CNT (NOT IMPLEMENTED)"); + } + + // STR (Start Operation) + if ((up & DMAC::CCR_STR)) { + StartTransfer(chan); + } + + ChangeInterrupt(); } +// CSR.ACT ビットの状態を変更。 +void +DMACDevice::ChangeACT(DMACChan *chan, bool active) +{ + chan->active = active; + + // チャンネルの ACT を変更した結果、 + // トータルでアクティブかどうかを Syncer に通知する。 + bool dmac_active = + channels[0]->active || + channels[1]->active || + channels[2]->active || + channels[3]->active; + syncer->NotifyDMACActive(dmac_active); +} + +// 転送開始 +void +DMACDevice::StartTransfer(DMACChan *chan) +{ + // Operation Timing Error (ii) + // CSR の ACT,COC,BTC,NDT,ERR が立ってたら開始しない + if (chan->active || (chan->csr & 0xf0) != 0) { + Error(chan, DMAC::CER_TIMING); + return; + } + + // ACT を立てたら STR を下げる。 + ChangeACT(chan, true); + chan->str = false; + + // XXX あとで移動する + switch (chan->GetMAC()) { + case DMAC::SCR_COUNT_UP: + chan->mac = 1; + break; + case DMAC::SCR_COUNT_DOWN: + chan_putlog(chan, 0, "SCR:MAC CountDown Mode (NOT SUPPORTED)"); + chan->mac = -1; + break; + default: + chan->mac = 0; + break; + } + switch (chan->GetDAC()) { + case DMAC::SCR_COUNT_UP: + chan->dac = 1; + break; + case DMAC::SCR_COUNT_DOWN: + chan_putlog(chan, 0, "SCR:DAC CountDown Mode (NOT SUPPORTED)"); + chan->dac = -1; + break; + default: + chan->dac = 0; + break; + } + + chan->data.Clear(); + + // 転送モード + uint reqg = chan->GetREQG(); + switch (reqg) { + case DMAC::REQG_AUTO_LIM: + VMPANIC("REQG_AUTO_LIM 未実装"); + break; + + case DMAC::REQG_AUTO_MAX: + // 自発的に転送を開始する + chan->req = true; + break; + + case DMAC::REQG_EXTERNAL: + // 外部リクエスト転送。ここでは何もしなくてよい + break; + + case DMAC::REQG_AUTOFIRST: + VMPANIC("未実装 REQG"); + break; + + default: + VMPANIC("corrupted reqg=%u", reqg); + } + + // チェインモードなら初回読み込み。 + // 通常モードなら何もしなくていいので、ログを出すだけ。 + switch (chan->GetCHAIN()) { + case DMAC::CHAIN_reserved: + // とりあえず DISABLED と同じ動作にしておくか。 + chan_putlog(chan, 1, "Undefined CHAIN=1 (ignored)"); + FALLTHROUGH; + case DMAC::CHAIN_DISABLED: + chan_putlog(chan, 1, "%s", MakeStartLog(chan).c_str()); + break; + + case DMAC::CHAIN_ARRAY: + chan_putlog(chan, 0, "Array Chaining Mode (NOT SUPPORTED)"); + return; + + case DMAC::CHAIN_LINKARRAY: + // ブロック開始時に BAR から1エントリ読んで、そのリンクアドレスが + // $00000000 ならこのブロックが最後と判断する、と書いてあるように + // 読めるので、開始時 BAR が $0000000 なら 0 番地から読むだろうか。 + if (LoadLinkArray(chan) == false) { + return; + } + break; + + default: + __unreachable(); + } + + if (event->IsRunning() == false) { + CallAfter(StartCallback, 0, 0); + } +} + +// 転送開始ログを返す。 +// 全パラメータは無理なので概要だけ表示。 +std::string +DMACDevice::MakeStartLog(DMACChan *chan) +{ + static const char * const countstr[] = { + "-", + "", + "+", + }; + + std::string scrmsg; + if (chan->GetDIR() == DMAC::DIR_MtoD) { + scrmsg = string_format("$%06x%s to $%06x%s", + chan->mar, countstr[chan->mac + 1], + chan->dar, countstr[chan->dac + 1]); + } else { + scrmsg = string_format("$%06x%s to $%06x%s", + chan->dar, countstr[chan->dac + 1], + chan->mar, countstr[chan->mac + 1]); + } + + return string_format("Start %s mtc=$%04x", scrmsg.c_str(), chan->mtc); +} + +// BAR の指すリンクアレイチェインのブロックエントリを読み込む。 +// 読めたら true を返す。エラーなら Error をセットして false を返す。 +bool +DMACDevice::LoadLinkArray(DMACChan *chan) +{ + uint32 orig_bar = chan->bar; + busdata data; + + // リンクアレイチェインの BAR は偶数から始まり、 + // +$0.L アドレス(MAR) + // +$4.W カウント(MTC) + // +$6.L 次のリンクアドレス(BAR) + + busaddr bar_addr = busaddr(chan->bar) | chan->bfc; + if ((bar_addr.Addr() & 1U) != 0) { + goto error; + } + + data = ReadMem4(bar_addr); + if (__predict_false(data.IsBusErr())) { + goto error; + } + chan->mar = data.Data(); + + bar_addr += 4; + bar_addr.ChangeSize(2); + data = ReadMem(bar_addr); + if (__predict_false(data.IsBusErr())) { + goto error; + } + chan->mtc = data.Data(); + + bar_addr += 2; + data = ReadMem4(bar_addr); + if (__predict_false(data.IsBusErr())) { + goto error; + } + chan->bar = data.Data(); + + if (__predict_false(chan->loglevel >= 1)) { + chan_putlog(chan, 2, "%s bar=%06x next=%06x", + __func__, orig_bar, chan->bar); + std::string msg = MakeStartLog(chan); + chan->putlogn("%s", msg.c_str()); + } + + return true; + + error: + Error(chan, DMAC::CER_ADDR_BAR); + return false; +} + +// 今回転送するチャンネルを決定して返す。なければ NULL を返す。 +DMACChan * +DMACDevice::SelectChannel() +{ + int ch = -1; + uint8 prio = 255; + + for (auto& chan : channels) { + if (chan->active) { + uint8 p = chan->priority; + if (p < prio) { + prio = p; + // 実効プライオリティのラウンドロビン用のところを上げておく + chan->priority = (p & 0xf0) | ((p & 0x0f) >> 1); + ch = chan->ch; + } + } + } + if (ch == -1) { + // 転送チャンネルはもうないのでイベントは停止したままにする + event->SetName("DMAC"); + return NULL; + } else { + DMACChan *chan = channels[ch].get(); + event->SetName(string_format("DMAC #%u", chan->ch)); + return chan; + } +} + +// 転送開始 (MTC 1回分の開始) +void +DMACDevice::StartCallback(Event *ev) +{ + // チャンネルを決定 + auto chan = SelectChannel(); + if (chan == NULL) { + return; + } + + // 実効プライオリティを同順位の最後に回す + chan->priority |= 0x08; + + chan->retry = 0; + + // XXX バスアービトレーションとか + + // 転送シーケンス決定 + chan->seq.clear(); + chan->seq_index = 0; + if (chan->GetDIR() == DMAC::DIR_MtoD) { + if (chan->GetDPS() == DMAC::DPS_8BIT) { + if (chan->GetSIZE() == DMAC::SIZE_32BIT) { + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D8); + chan->seq.push_back(WR_D8); + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D8); + chan->seq.push_back(WR_D8); + } else if (chan->GetSIZE() == DMAC::SIZE_16BIT) { + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D8); + chan->seq.push_back(WR_D8); + } else { + chan->seq.push_back(RD_M8); + chan->seq.push_back(WR_D8); + } + } else { + // DPS==16 + if (chan->GetSIZE() == DMAC::SIZE_32BIT) { + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D16); + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D16); + } else if (chan->GetSIZE() == DMAC::SIZE_16BIT) { + chan->seq.push_back(RD_M16); + chan->seq.push_back(WR_D16); + } else { + chan->seq.push_back(RD_M8); + chan->seq.push_back(WR_D8); + } + } + } else { // DtoM + if (chan->GetDPS() == DMAC::DPS_8BIT) { + if (chan->GetSIZE() == DMAC::SIZE_32BIT) { + chan->seq.push_back(RD_D8); + chan->seq.push_back(RD_D8); + chan->seq.push_back(WR_M16); + chan->seq.push_back(RD_D8); + chan->seq.push_back(RD_D8); + chan->seq.push_back(WR_M16); + } else if (chan->GetSIZE() == DMAC::SIZE_16BIT) { + chan->seq.push_back(RD_D8); + chan->seq.push_back(RD_D8); + chan->seq.push_back(WR_M16); + } else { + chan->seq.push_back(RD_D8); + chan->seq.push_back(WR_M8); + } + } else { + // DPS==16 + if (chan->GetSIZE() == DMAC::SIZE_32BIT) { + chan->seq.push_back(RD_D16); + chan->seq.push_back(WR_M16); + chan->seq.push_back(RD_D16); + chan->seq.push_back(WR_M16); + } else if (chan->GetSIZE() == DMAC::SIZE_16BIT) { + chan->seq.push_back(RD_D16); + chan->seq.push_back(WR_M16); + } else { + chan->seq.push_back(RD_D8); + chan->seq.push_back(WR_M8); + } + } + } + + if (__predict_false(chan->loglevel >= 5)) { + std::string ss; + for (const auto op : chan->seq) { + ss += ' '; + ss += seqname[op]; + } + chan->putlogn("%s seq:%s", __func__, ss.c_str()); + } + + CallAfter(TransferCallback, 1, 0); +} + +// 転送処理 (内部シーケンス) +void +DMACDevice::TransferCallback(Event *ev) +{ + // チャンネルを決定 + auto chan = SelectChannel(); + if (chan == NULL) { + return; + } + + uint op = chan->seq[chan->seq_index]; + chan_putlog(chan, 5, "%s [%u] %s", + __func__, chan->seq_index, seqname[op]); + + uint64 data; + busdata r; + + // デバイスアクセスなら ACK#n 信号と DONE 信号のドライブ + switch (op) { + case RD_D8: + case RD_D16: + case WR_D8: + case WR_D16: + if (chan->req == false) { + // REQ が立つまで待つ。構造上ポーリングするしか…。 + switch (chan->ch) { + case 0: + case 1: + case 2: + chan_putlog(chan, 0, "%s Wait REQ", __func__); + ev->time = 1_usec; + break; + case 3: + // ADPCM は最短でも 64_usec とかなので長めでよい。 + ev->time = 1_usec; + break; + default: + __unreachable(); + } + scheduler->StartEvent(ev); + return; + } + AssertACK(chan); + break; + default: + break; + } + + // パック動作が起きるケースなら op を差し替える。 + if (chan->GetSIZE() == DMAC::SIZE_8BIT_PACK) { + if (op == RD_M8) { + if (chan->mtc > 1 && chan->mar % 2 == 0 && chan->GetMAC() != 0) { + if (chan->data.Length() == 0) { + op = RD_M16; + } else { + op = NOP; + } + } + } + if (op == WR_M8) { + if (chan->data.Length() >= 2) { + op = WR_M16; + } else { + if (chan->mtc > 1 && chan->mar % 2 == 0 && chan->GetMAC() != 0){ + op = NOP; + } + } + } + } + + // アドレスを生成。 + busaddr addr; + switch (op) { + case RD_M8: + case RD_M16: + case WR_M8: + case WR_M16: + addr = busaddr(chan->mar) | chan->mfc; + break; + case RD_D8: + case RD_D16: + case WR_D8: + case WR_D16: + addr = busaddr(chan->dar) | chan->dfc; + break; + default: + break; + } + + // サイズを指定。 + switch (op) { + case RD_M8: + case RD_D8: + case WR_M8: + case WR_D8: + addr.ChangeSize(1); + break; + case RD_M16: + case RD_D16: + case WR_M16: + case WR_D16: + addr.ChangeSize(2); + break; + default: + break; + } + + + // 書き込みならキューからデータを取得。 + switch (op) { + case WR_M8: + case WR_D8: + data = chan->data.Dequeue(); + break; + case WR_M16: + case WR_D16: + data = chan->data.Dequeue() << 8; + data |= chan->data.Dequeue(); + break; + default: + data = 0; + break; + } + + // ユーザ空間へのアクセスが可能かどうかここで調べる。 + // 本来は DMAC ではなくメインバスで管理すべきことだが、パフォーマンスの + // 観点から MPU 側に FC2 カットによる可動部を増やしたくないので、 + // アクセス頻度の低い DMAC 側で肩代わりしている。 + bool accessible = + __predict_true(addr.IsSuper() || useraccess[addr.Addr() / 0x2000]); + + // 1回分のアクセス。 + switch (op) { + case RD_M8: + case RD_D8: + case RD_M16: + case RD_D16: + if (accessible) { + r = ReadMem(addr); + } else { + r.SetBusErr(); + } + break; + case WR_M8: + case WR_D8: + case WR_M16: + case WR_D16: + if (accessible) { + r = WriteMem(addr, data); + } else { + r.SetBusErr(); + } + break; + case NOP: + r = 0; + break; + default: + VMPANIC("corrupted op=%u", op); + } + + if (__predict_false(loglevel >= 4)) { + TransferLog(chan, op, addr, r, data); + } + + // デバイスアクセスなら ACK#n 信号と DONE 信号のドライブ + switch (op) { + case RD_D8: + case RD_D16: + case WR_D8: + case WR_D16: + NegateACK(chan); + break; + default: + break; + } + + // バスエラー(もしくはリトライ)か。ACK を下ろした後で行う。 + if (r.IsOK() == false) { + TransferError(chan, op, r, data); + return; + } + + // 読み込んだデータをキューに投入。バスエラー判定後に行う。 + switch (op) { + case RD_M8: + case RD_D8: + chan->data.Enqueue(r.Data()); + break; + case RD_M16: + case RD_D16: + chan->data.Enqueue(r.Data() >> 8); + chan->data.Enqueue(r.Data() & 0xff); + break; + default: + break; + } + + // カウンタを更新。バスエラー判定後に行う。 + switch (op) { + case RD_M8: + case WR_M8: + chan->mar += chan->mac; + break; + case RD_M16: + case WR_M16: + chan->mar += chan->mac * 2; + break; + case RD_D8: + case WR_D8: + chan->dar += chan->dac * 2; + break; + case RD_D16: + case WR_D16: + chan->dar += chan->dac * 2; + break; + case NOP: + break; + default: + VMPANIC("corrupted op=%u", op); + } + + // シーケンスを一つ進める。完了すれば転送1回分。 + chan->seq_index++; + if (chan->seq_index < chan->seq.size()) { + // XXX ディレイはあとから見直す + CallAfter(TransferCallback, 4, r.GetWait()); + return; + } + + // ここで転送1回分が完了。 + chan->mtc -= 1; + + bool complete = false; + if (__predict_false(chan->mtc == 0)) { + // MTC 分転送すれば1ブロック完了。 + switch (chan->GetCHAIN()) { + case DMAC::CHAIN_DISABLED: + case DMAC::CHAIN_reserved: + case DMAC::CHAIN_ARRAY: // not yet + complete = true; + break; + + case DMAC::CHAIN_LINKARRAY: + // 次のブロックを BAR から読む。BAR が 0 ならここで終了。 + if (chan->bar == 0) { + complete = true; + } else { + if (LoadLinkArray(chan) == false) { + return; + } + } + break; + default: + __unreachable(); + } + } + + if (complete == false) { + // XXX ディレイはあとから見直す + CallAfter(StartCallback, 4, r.GetWait()); + } else { + // すべての転送が完了。 + chan_putlog(chan, 1, "Complete Transfer"); + chan->csr |= DMAC::CSR_COC; + ChangeACT(chan, false); + ChangeInterrupt(); + } +} + +// 転送エラー +void +DMACDevice::TransferError(DMACChan *chan, uint op, busdata r, uint32 data) +{ + if (r.IsRetry()) { + // DTACK が出ていない (SPC)。 + // 本当は DTACK が出たことをコールバックしてくれれば + // 効率がいいのだがポーリングでもかまわんだろう。 + chan->retry++; + if (chan->retry < 100) { + // 書き込みがリトライになったら仕方ないので、今書こうとして + // 取り出したデータをキューに戻してから再実行。うーんこの。 + switch (op) { + case WR_M8: + case WR_D8: + chan->data.PushFront(data); + break; + case WR_M16: + case WR_D16: + chan->data.PushFront(data); + chan->data.PushFront(data >> 8); + break; + default: + break; + } + + // 現在のイベントを再実行 + event->time = 1 * 80_nsec; + scheduler->StartEvent(event); + return; + } else { + // タイムアウトしたらバスエラーにフォールスルー + chan_putlog(chan, 3, "retry exceeds"); + } + } + + // バスエラー + switch (op) { + case RD_M8: + case RD_M16: + case WR_M8: + case WR_M16: + Error(chan, DMAC::CER_BUS_MAR); + break; + case RD_D8: + case RD_D16: + case WR_D8: + case WR_D16: + Error(chan, DMAC::CER_BUS_DAR); + break; + default: + VMPANIC("corrupted op=%u", op); + } +} + +// 1転送ごとのログを出力。 +// ログを出力することが決まってから呼ばれる。 +void +DMACDevice::TransferLog(DMACChan *chan, uint op, busaddr addr, + busdata r, uint64 data) +{ + // Nop + // Read $%06x -> $xx + // Read $%06x -> BusErr + // Read $%06x -> $xx (1 retried) + // Write $%06x <- $xx + // Write $%06x <- $xx BusErr + // Write $%06x <- $xx (1 retried) + + if (op == NOP) { + chan->putlogn("Nop"); + return; + } + + if (r.IsRetry()) { + return; + } + + const char *act; + const char *dir; + switch (op) { + case RD_M8: + case RD_D8: + case RD_M16: + case RD_D16: + act = "Read "; + dir = "->"; + break; + case WR_M8: + case WR_D8: + case WR_M16: + case WR_D16: + act = "Write"; + dir = "<-"; + break; + default: + act = ""; + dir = ""; + break; + } + + std::string str = string_format("%s $%06x %s ", act, addr.Addr(), dir); + + switch (op) { + case RD_M8: + case RD_D8: + if (r.IsOK()) { + str += string_format("$%02x", r.Data()); + } + break; + case RD_M16: + case RD_D16: + if (r.IsOK()) { + str += string_format("$%04x", r.Data()); + } + break; + case WR_M8: + case WR_D8: + str += string_format("$%02x", (uint32)data); + break; + case WR_M16: + case WR_D16: + str += string_format("$%04x", (uint32)data); + break; + default: + break; + } + + if (r.IsBusErr()) { + str += " BusErr"; + } + + if (chan->retry != 0) { + str += string_format(" (%u retried)", chan->retry); + } + + chan->putlogn("%s", str.c_str()); +} + +// エラー発生 +void +DMACDevice::Error(DMACChan *chan, uint8 errcode) +{ + // データシート p.43 + chan->cer = errcode; + chan->csr |= DMAC::CSR_COC | DMAC::CSR_ERR; + ChangeACT(chan, false); + chan->str = false; + chan->cnt = false; + ChangeInterrupt(); + CallAfter(StartCallback, 0, 0); +} + +// ACK#n 信号線をアサート +void +DMACDevice::AssertACK(DMACChan *chan) +{ + switch (chan->ch) { + case 0: + fdc->AssertDACK(chan->mtc == 1); + break; + case 1: + case 2: + // 接続されていない + break; + case 3: + adpcm->AssertDACK(); + break; + default: + __unreachable(); + } +} + +// ACK#n 信号線をネゲート +void +DMACDevice::NegateACK(DMACChan *chan) +{ + switch (chan->ch) { + case 0: + fdc->NegateDACK(); + break; + case 1: + case 2: + // 接続されていない + break; + case 3: + // 不要。 + break; + default: + __unreachable(); + } +} + +// REQ#n 信号線アサート (外部から呼ばれる) +void +DMACDevice::AssertREQ(uint ch) +{ + assert(ch < 4); + + DMACChan *chan = channels[ch].get(); + chan->req = true; + + // #3(ADPCM) の REQ は PCL にも繋がっている + if (ch == 3) { + chan->pcl_pin = true; + } + + if (event->IsRunning() == false) { + scheduler->StartEvent(event); + } +} + +// REQ#n 信号線ネゲート (外部から呼ばれる) +void +DMACDevice::NegateREQ(uint ch) +{ + assert(ch < 4); + + DMACChan *chan = channels[ch].get(); + chan->req = false; + + // #3(ADPCM) の REQ は PCL にも繋がっている + if (ch == 3) { + chan->pcl_pin = false; + } +} + +// ソフトウェアアボート +void +DMACDevice::AbortTransfer(DMACChan *chan) +{ + chan_putlog(chan, 2, "CCR SAB Software Abort"); + + // %1 を書き込むことで実際には SAB はセットされるが、ERR が立つと + // SAB をクリアする動作のため、書き込んだ %1 が読めることはない。 + // ここでは SAB ビットのセットを省略。 + + chan->str = false; + chan->cnt = false; + ChangeACT(chan, false); + + chan->cer = DMAC::CER_SOFT_ABORT; + // ERR ビットが立つと SAB をクリアする + chan->csr |= DMAC::CSR_ERR; + chan->sab = false; + ChangeInterrupt(); +} + +// メインメモリから 1 or 2 バイト読み込む。 +busdata +DMACDevice::ReadMem(busaddr addr) +{ + busdata data = mainbus->Read(addr); + + // IODevice からの読み込みはポートサイズ単位で行う仕様なので、 + // ここでダイナミックバスサイジングの要領で必要な部分を抜き出す。 + // 実際の HD63450 は 68000 バス用のデバイスなので、このあたりの + // バス変換を YUKI ちゃんなどの周辺回路がやってるはず。 + uint64 rearranged = MainbusDevice::DYNAMIC_BUS_SIZING_R(addr, data); + data.ChangeData(rearranged); + + return data; +} + +// メインメモリから4バイト読み込む。 +// Size フィールドは指定しなくてもよい。 +busdata +DMACDevice::ReadMem4(busaddr addr) +{ + addr.ChangeSize(2); + + busdata hi = ReadMem(addr); + if (__predict_false(hi.IsBusErr())) { + return hi; + } + + addr += 2; + busdata lo = ReadMem(addr); + if (__predict_false(lo.IsBusErr())) { + return lo; + } + + busdata data((hi.Data() << 16) | lo.Data()); + return data; +} + +// メインメモリに書き込む。 +// ほぼ対称性のためだけ。 +busdata +DMACDevice::WriteMem(busaddr addr, uint32 data) +{ + return mainbus->Write(addr, data); +} + +// インデックス化されたアドレス start から end (の手前まで) のユーザ空間 +// アクセスの可否を設定する。x68kio から呼ばれる。 +// accessible true なら (MPU の FC2 ピンによらず) ユーザアクセス可能。 +// アドレスは 8KB 単位なので start=1 ならアドレスは $2000。 +void +DMACDevice::SetUdevice(uint32 start, uint32 end, bool accessible) +{ + for (int i = start; i < end; i++) { + useraccess[i] = accessible; + } +} + +// 割り込み信号線の状態を変える。 +void +DMACDevice::ChangeInterrupt() +{ + bool irq = false; + + for (auto& chan : channels) { + if (chan->int_enable && chan->IsINTR()) { + irq = true; + } + } + interrupt->ChangeINT(this, irq); +} + +// 割り込みアクノリッジ +busdata +DMACDevice::InterruptAcknowledge() +{ + int top = -1; + uint8 prio = 255; + + // 割り込みを発生させているトッププライオリティのチャンネルを検索 + for (auto& chan : channels) { + if (chan->int_enable && chan->IsINTR()) { + if (chan->priority < prio) { + prio = chan->priority; + top = chan->ch; + } + } + } + + if (__predict_true(top >= 0)) { + DMACChan *chan = channels[top].get(); + if ((chan->csr & DMAC::CSR_ERR)) { + return chan->eiv; + } else { + return chan->niv; + } + } else { + return BusData::BusErr; + } +} + +// +// チャンネル +// + +// コンストラクタ +DMACChan::DMACChan(int ch_, const char *desc_) + : inherited(OBJ_DMAC_CH(ch_)) +{ + ch = ch_; + desc = desc_; + + ClearAlias(); + AddAlias(string_format("DMAC%u", ch)); +} + +// デストラクタ +DMACChan::~DMACChan() +{ +} + +// CSR を取得する +uint8 +DMACChan::GetCSR() const +{ + uint32 val; + + val = csr & 0xf0; + if (active) { + val |= DMAC::CSR_ACT; + } + // PCT は PCL が High から Low に変わるとセット + if (pcl_prev == true && pcl_pin == false) { + val |= DMAC::CSR_PCT; + } + if (pcl_pin) { + val |= DMAC::CSR_PCS; + } + return val; +} + +// DCR を取得する +uint8 +DMACChan::GetDCR() const +{ + uint8 val; + + val = (xrm << 6); + val |= (dtyp << 4); + val |= (dps << 3); + val |= dcr_pcl; + + return val; +} + +// DCR を設定する +void +DMACChan::SetDCR(uint8 val) +{ + xrm = (val >> 6); + dtyp = (val >> 4) & 3; + dps = (val >> 3) & 1; + dcr_pcl = val & 3; +} + +// OCR を取得する +uint8 +DMACChan::GetOCR() const +{ + uint8 val; + + val = (dir << 7); + val |= (size << 4); + val |= (chain << 2); + val |= reqg; + + return val; +} + +// OCR を設定する +void +DMACChan::SetOCR(uint8 val) +{ + dir = (val >> 7); + size = (val >> 4) & 3; + chain = (val >> 2) & 3; + reqg = val & 3; +} + +// SCR を取得する +uint8 +DMACChan::GetSCR() const +{ + uint8 val; + + val = scr_mac << 2; + val |= scr_dac; + + return val; +} + +// SCR を設定する +void +DMACChan::SetSCR(uint8 val) +{ + scr_mac = (val >> 2) & 3; + scr_dac = val & 3; +} + +// CCR を取得する +uint8 +DMACChan::GetCCR() const +{ + uint8 val = 0; + + if (str) { + val |= DMAC::CCR_STR; + } + if (cnt) { + val |= DMAC::CCR_CNT; + } + if (hlt) { + val |= DMAC::CCR_HLT; + } + if (sab) { + val |= DMAC::CCR_SAB; + } + if (int_enable) { + val |= DMAC::CCR_INT; + } + + return val; +} + +// CCR を設定する (値を置く以上のことはしない) +void +DMACChan::SetCCR(uint8 val) +{ + str = (val & DMAC::CCR_STR); + cnt = (val & DMAC::CCR_CNT); + hlt = (val & DMAC::CCR_HLT); + sab = (val & DMAC::CCR_SAB); + int_enable = (val & DMAC::CCR_INT); +} + +// NIV を設定する +void +DMACChan::SetNIV(uint8 val) +{ + niv = val; +} + +// EIV を設定する +void +DMACChan::SetEIV(uint8 val) +{ + eiv = val; +} + +// MFC を設定する +void +DMACChan::SetMFC(uint8 val) +{ + mfc = busaddr::FC(val & 0x07); +} + +// CPR を設定する +void +DMACChan::SetCPR(uint8 val) +{ + cpr = val & 0x03; + // 実効プライオリティの上位4bitは cpr に等しい + priority = cpr << 4; +} + +// DFC を設定する +void +DMACChan::SetDFC(uint8 val) +{ + dfc = busaddr::FC(val & 0x07); +} + +// BFC を設定する +void +DMACChan::SetBFC(uint8 val) +{ + bfc = busaddr::FC(val & 0x07); +} + +// レジスタ名 (バイト) +/*static*/ const char * const +DMACDevice::regname1[0x40] = { + "CSR", // 00 + "CER", // 01 + NULL, // 02 + NULL, // 03 + "DCR", // 04 + "OCR", // 05 + "SCR", // 06 + "CCR", // 07 + NULL, // 08 + NULL, // 09 + "MTC:H", // 0a + "MTC:L", // 0b + "MAR:0", // 0c + "MAR:1", // 0d + "MAR:2", // 0e + "MAR:3", // 0f + NULL, // 10 + NULL, // 11 + NULL, // 12 + NULL, // 13 + "DAR:0", // 14 + "DAR:1", // 15 + "DAR:2", // 16 + "DAR:3", // 17 + NULL, // 18 + NULL, // 19 + "BTC:H", // 1a + "BTC:L", // 1b + "BAR:0", // 1c + "BAR:1", // 1d + "BAR:2", // 1e + "BAR:3", // 1f + NULL, // 20 + NULL, // 21 + NULL, // 22 + NULL, // 23 + NULL, // 24 + "NIV", // 25 + NULL, // 26 + "EIV", // 27 + NULL, // 28 + "MFC", // 29 + NULL, // 2a + NULL, // 2b + NULL, // 2c + "CPR", // 2d + NULL, // 2e + NULL, // 2f + NULL, // 30 + "DFC", // 31 + NULL, // 32 + NULL, // 33 + NULL, // 34 + NULL, // 35 + NULL, // 36 + NULL, // 37 + NULL, // 38 + "BFC", // 39 + NULL, // 3a + NULL, // 3b + NULL, // 3c + NULL, // 3d + NULL, // 3e + NULL, // 3f (GCR は別処理) +}; + +// レジスタ名 (ワード) +/*static*/ const char * const +DMACDevice::regname2[0x40 / 2] = { + "CSR:CER", // 00 + NULL, // 02 + "DCR:OCR", // 04 + "SCR:CCR", // 06 + NULL, // 08 + "MTC", // 0a + "MAR:H", // 0c + "MAR:L", // 0e + NULL, // 10 + NULL, // 12 + "DAR:H", // 14 + "DAR:L", // 16 + NULL, // 18 + "BTC", // 1a + "BAR:H", // 1c + "BAR:L", // 1e + NULL, // 20 + NULL, // 22 + "NIV(W)", // 24 + "EIV(W)", // 26 + "MFC(W)", // 28 + NULL, // 2a + "CPR(W)", // 2c + NULL, // 2f + "DFC(W)", // 30 + NULL, // 32 + NULL, // 34 + NULL, // 36 + "BFC(W)", // 38 + NULL, // 3a + NULL, // 3c + NULL, // 3e (GCR は別処理) +}; + +/*static*/ const char * const +DMACDevice::seqname[DMACDevice::SEQ_MAX] = +{ + "NOP", + "RD_M8", + "RD_M16", + "RD_D8", + "RD_D16", + "WR_M8", + "WR_M16", + "WR_D8", + "WR_D16", +};