File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / mainram.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// メインメモリ
//

// MainRAM はロングワード単位でホストバイトオーダ配置なので、
// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。

#include "mainram.h"
#include "config.h"
#include "mainapp.h"
#include "mpu.h"
#include "prom.h"

// コンストラクタ
MainRAMDevice::MainRAMDevice()
	: inherited(OBJ_MAINRAM)
{
	clear_on_boot = true;
}

// デストラクタ
MainRAMDevice::~MainRAMDevice()
{
}

// 初期化
bool
MainRAMDevice::Init()
{
	const ConfigItem& item = gConfig->Find("ram-size");
	int ram_size_MB = item.AsInt();
	if (ram_size_MB < 1) {
		item.Err();
		return false;
	}

	uint64 clock_tsec = mpu->GetClock_tsec();

	// メモリの下限以外の制約は機種による。
	switch (gMainApp.GetVMType()) {
	 case VMType::X68030:
		// メインメモリは 12MB まで。
		// 内部は 8KB 単位で処理できるが設定が 1MB 単位。
		if (ram_size_MB > 12) {
			item.Err("Configurable maximum main memory size is 12 [MB]");
			return false;
		}

		// ウェイトはシステムポートが設定するのでここでは不要。
		// バースト用の線は回路図によると配線されていないようだ。
		break;

	 case VMType::LUNA1:
	 {
		// 16MB までは、実機に合わせて 4MB 単位とする。
		// see http://wiki.netbsd.org/ports/luna68k/luna68k_info/#hardware
		//
		// 16MB を超える増設 (or 魔改造) 部分は 1MB 単位とする。
		if (ram_size_MB <= 16) {
			if (ram_size_MB % 4 != 0) {
				item.Err("For 16MB or less, must be specified in 4 [MB] unit");
				return false;
			}
		} else if (ram_size_MB > 512) {
			item.Err("Configurable maximum RAM size is 512 [MB]");
			return false;
		}

		// 実 ROM のメモリチェックは 32KB だかそのくらいずつ行われ、
		// 最後に番兵として NOP 領域 (BusError ではなく) が必要。
		// 現行の構成をあまり変えない範囲なら厳密には (256MB - 32KB) が
		// 上限だが、サイズ指定が 1MB 単位なので 255MB を上限にする。
		auto prom = dynamic_cast<PROMDevice *>(GetPROMDevice());
		if (prom) {
			if (ram_size_MB > 255) {
				item.Err("(Warning) Maximum RAM size with PROM is 255[MB]; "
					"Clip RAM size to 255[MB].");
				ram_size_MB = 255;
			}
		}

		// 通常サイクルは1ウェイトらしい?
		// バーストウェイトは LUNA-88K の取説からの推論。
		normal_wait = busdata::Wait(1 * clock_tsec);
		burst_wait  = busdata::Wait(4 * clock_tsec);
		break;
	 }

	 case VMType::LUNA88K:
	 {
		// 64MB までは、実機に合わせて 16MB 単位とする。
		//
		// 64MB を超える増設 (or 魔改造) 部分は未確認。
		if (ram_size_MB <= 64) {
			if (ram_size_MB % 16 != 0) {
				item.Err("For 64MB or less, must be specified in 16 [MB] unit");
				return false;
			}
		} else if (ram_size_MB > 512) {
			item.Err("Configurable maximum RAM size is 512 [MB]");
			return false;
		}

		// PROM 1.20 では 240MB を上限にする。
		// 0x0f00'0000 (ちょうど 240MB 目) へのアクセスでバスエラーが
		// 出ることを CMMU のアドレス変換チェックに使っているため。
		auto prom = dynamic_cast<PROMDevice *>(GetPROMDevice());
		if (prom && prom->GetROMVer() == 120) {
			if (ram_size_MB > 240) {
				item.Err("(warning) Maximum RAM size with PROM 1.20 is 240[MB];"
					" Clip RAM size to 240[MB].");
				ram_size_MB = 240;
			}
		}

		// 取扱説明書p.21 に、CMMU からのアクセスタイムが
		// リード(4byte)で 200ns、バーストリード(16byte)で 320ns
		// ライト(4byte)で 160ns、バーストライト(16byte)で 280ns とある。
		// 25MHz は 40ns/clock なのでそれぞれ、
		// リード(4byte)で 5 clock、バーストリード(16byte)で 8 clock
		// ライト(4byte)で 4 clock、バーストライト(16byte)で 7 clock となる。
		// これは CMMU 以降のアクセスタイムらしいので、
		// リード3クロック、ライト2クロック引いたものをウェイトとしてみる?
		// XXX 要確認。
		normal_wait = busdata::Wait(2 * clock_tsec);
		burst_wait  = busdata::Wait(5 * clock_tsec);
		break;
	 }

	 case VMType::NEWS:
		// 電源オン時に RAM に書き込んでいるため、クリアしない。
		clear_on_boot = false;
		// 知らんけど 1クロックくらい入れておく。
		normal_wait = busdata::Wait(1 * clock_tsec);
		// NWS-1750 はバースト転送をサポートしていないようだ。
		// http://www.ceres.dti.ne.jp/tsutsui/netbsd/port-news68k.html#19991203
		break;

	 case VMType::VIRT68K:
		// 1MB 単位で指定できる必要もないだろうから 16MB 単位に切り上げる。
		ram_size_MB = roundup(ram_size_MB, 16);

		// 最後の 16MB ブロックが I/O 空間なので、
		// メモリは最大で 4096 - 16 = 4080MB まで積める。
		if (ram_size_MB > 4080) {
			item.Err("Configurable maximum RAM size is 4080 [MB]");
			return false;
		}

		// ウェイト不要。
		normal_wait = busdata::Wait(0);
		burst_wait  = busdata::Wait(0);
		break;

	 default:
		PANIC("vmtype=%s not configured", gMainApp.GetVMTypeStr().c_str());
	}

	ram_size = (size_t)ram_size_MB * 1024 * 1024;
	mainram.reset(new(std::nothrow) uint8[ram_size]);
	if ((bool)mainram == false) {
		warnx("Could not allocate MainRAM(%zu bytes)", ram_size);
		return false;
	}

	// Human68k モードならここで一度だけメモリをゼロクリアする。
	// この時は Human68k::Init() が MainRAM にプログラムを転送するため
	// 電源オン時点で RAM を初期化してはいけない…。うーんこの…。
	// MainRAM::Init() が Human68k::Init() より先に呼ばれることは
	// VM_X68030 で指定してある。
	// 一方、通常モードなら VM リスタート(電源再投入) で前の内容を残さない
	// ために ResetHard(true) でメモリを (ゼロでなくてもいいので) クリアする
	// 必要がある。
	if (gMainApp.human_mode) {
		memset(&mainram[0], 0, ram_size);
		clear_on_boot = false;
	}

	return true;
}

// 電源オン/リセット
void
MainRAMDevice::ResetHard(bool poweron)
{
	if (poweron) {
		if (clear_on_boot) {
			// XXX ノイズを用意するべき
			memset(&mainram[0], 0, ram_size);
		}
	}

	// X68030 ではリセットで戻るはず。(LUNA は固定なので関係ない)
	if (gMainApp.IsX68030()) {
		normal_wait = busdata::Wait(0);
	}
}

busdata
MainRAMDevice::Read(busaddr addr)
{
	uint32 paddr = addr.Addr();
	busdata data;

	data  = *(uint32 *)&mainram[paddr & ~3];
	data |= normal_wait;
	data |= BusData::Size4;
	return data;
}

busdata
MainRAMDevice::Write(busaddr addr, uint32 data)
{
	uint32 paddr = addr.Addr();
	uint32 reqsize = addr.GetSize();
	uint32 datasize = std::min(4 - (paddr & 3U), reqsize);

	if (datasize == 4) {
		*(uint32 *)&mainram[paddr] = data;
	} else if (datasize == 1) {
		data >>= (reqsize - datasize) * 8;
		mainram[HLB(paddr)] = data;
	} else {
		data >>= (reqsize - datasize) * 8;
		for (int i = datasize - 1; i >= 0; i--) {
			mainram[HLB(paddr + i)] = data;
			data >>= 8;
		}
	}

	busdata r = normal_wait;
	r |= BusData::Size4;
	return r;
}

busdata
MainRAMDevice::ReadBurst16(busaddr addr, uint32 *dst)
{
	uint32 paddr = addr.Addr();
	putlog(4, "$%08x BurstRead", paddr);
	memcpy(dst, &mainram[paddr], 16);
	return burst_wait;
}

busdata
MainRAMDevice::WriteBurst16(busaddr addr, const uint32 *src)
{
	uint32 paddr = addr.Addr();
	putlog(4, "$%08x BurstWrite", paddr);
	memcpy(&mainram[paddr], src, 16);
	return burst_wait;
}

busdata
MainRAMDevice::Peek1(uint32 addr)
{
	return mainram[HLB(addr)];
}

bool
MainRAMDevice::Poke1(uint32 addr, uint32 data)
{
	if ((int32)data >= 0) {
		mainram[HLB(addr)] = data;
	}
	return true;
}

// 領域読み出し。
// addr から totallen までが領域内なら転送して true を返す。
// 領域外を指すなら何もせず false を返す。
bool
MainRAMDevice::ReadMem(uint32 addr, void *dst_, uint32 totallen)
{
	uint8 *dst = (uint8 *)dst_;
	uintptr_t u = (uintptr_t)addr;
	uint32 len;

	if (__predict_false(addr + totallen > GetSize())) {
		return false;
	}

	if (__predict_false(((u | (uintptr_t)dst) & 3) != 0)) {
		if (((u ^ (uintptr_t)dst) & 3) || totallen < 4) {
			len = totallen;
		} else {
			len = 4 - (uint32)(u & 3);
		}
		totallen -= len;
		for (; len > 0; len--) {
			*dst++ = mainram[HLB(addr)];
			addr++;
		}
	}

	len = totallen / 4;
	for (; len > 0; len--) {
		*(uint32 *)dst = be32toh(*(uint32 *)&mainram[addr]);
		dst += 4;
		addr += 4;
	}

	len = totallen & 3;
	for (; len > 0; len--) {
		*dst++ = mainram[HLB(addr)];
		addr++;
	}

	return true;
}

// 領域書き込み。
// addr から totallen までが領域内なら転送して true を返す。
// 領域外を指すなら何もせず false を返す。
bool
MainRAMDevice::WriteMem(uint32 addr, const void *src_, uint32 totallen)
{
	const uint8 *src = (const uint8 *)src_;
	uintptr_t u = (uintptr_t)addr;
	uint32 len;

	if (__predict_false(addr + totallen > GetSize())) {
		return false;
	}

	if (__predict_false(((u | (uintptr_t)src) & 3) != 0)) {
		if (((u ^ (uintptr_t)src) & 3) || totallen < 4) {
			len = totallen;
		} else {
			len = 4 - (uint32)(u & 3);
		}
		totallen -= len;
		for (; len > 0; len--) {
			mainram[HLB(addr)] = *src++;
			addr++;
		}
	}

	len = totallen / 4;
	for (; len > 0; len--) {
		*(uint32 *)&mainram[addr] = be32toh(*(const uint32 *)src);
		addr += 4;
		src += 4;
	}

	len = totallen & 3;
	for (; len > 0; len--) {
		mainram[HLB(addr)] = *src++;
		addr++;
	}

	return true;
}

// アクセスウェイト [clock] を設定。
// X68030 でシステムポートから呼ばれるため normal_wait のみ設定する。
void
MainRAMDevice::SetWait(uint32 wait_clock)
{
	normal_wait = busdata::Wait(wait_clock * mpu->GetClock_tsec());
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.