File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / mpu680x0.h
Revision 1.1.1.19 (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
//

//
// MPU (MC68030/MC68040)
//

// ログレベル基準。
// 1: 状態変化などの概要
// 2: 命令などによる詳細
// 3: メモリアクセスの概要
// 4: メモリアクセスの詳細(MMU等)
// 5: デバッガアクセスのデバッグ用

#pragma once

#include "mpu.h"
#include "branchhistory.h"
#include "bus.h"
#include "m680x0.h"
#include "m68030mmu.h"
#include <array>

class InterruptDevice;
class m68030Cache;
struct m68030CacheLine;
enum class m68030MPUType;
class m68040TT;
class m68040TC;
class m68040ATC;
class m68040ATCEntry;

class MPU680x0Device : public MainMPUDevice
{
	using inherited = MainMPUDevice;
	friend class m68030PageTableOp;

 public:
	MPU680x0Device();
	~MPU680x0Device() override;

	bool Init() override;
	void ResetHard(bool poweron) override;

	// 現在の PPC を取得
	uint32 GetPPC() const override { return ppc; }

	// 現在の VBR を取得
	uint32 GetVBR() const override { return reg.vbr; }

	// MPU 種別を取得
	m680x0MPUType GetMPUType() const { return mpu_type; }

	// FPU
	m680x0FPUType GetFPUType() const { return fpu_type; }
	bool HasFPU() const	{ return fpu_type.Is4060FPU() || fpu_type.Is6888x(); }

	// MPU 名を取得
	const char *GetMPUName() const override { return mpu_name; }

	bool IsSuper() const	{ return reg.s; }
	bool IsMaster() const	{ return reg.m; }

	// 割り込みレベルが変わったことを MPU に通知
	void Interrupt(int level) override;

	// A-Line, F-Line 命令エミュレーションのコールバックを指定する。
	// (Human68k エミュレーションで使用)
	void SetALineCallback(bool (*callback)(MPU680x0Device*, void*), void *arg);
	void SetFLineCallback(bool (*callback)(MPU680x0Device*, void*), void *arg);

	// バーストアクセスをサポートするか
	void EnableBurstAccess(bool enable) { has_burst_access = enable; }

	void SetCI() override {
		bus.ci = true;
	}

	// ホールト
	void Halt();

	// 基本アクセス。内部から使用する。
	// アドレスは論理アドレスで、空間は現在の命令/データ空間。
	// バスエラーなら C++ の例外をスローする。
	virtual uint32 fetch_2() = 0;
	virtual uint32 fetch_4() = 0;
	uint32 read_n(uint32 laddr, uint size) {
		busaddr addr =
			busaddr(laddr) | fc_data | BusAddr::R | busaddr::Size(size);
		return read_data(addr);
	}
	void write_n(uint32 laddr, uint size, uint32 data) {
		busaddr addr =
			busaddr(laddr) | fc_data | BusAddr::W | busaddr::Size(size);
		write_data(addr, data);
	}
	uint32 read_1(uint32 laddr) { return read_n(laddr, 1); }
	uint32 read_2(uint32 laddr) { return read_n(laddr, 2); }
	uint32 read_4(uint32 laddr) { return read_n(laddr, 4); }
	void write_1(uint32 laddr, uint32 data) { write_n(laddr, 1, data); }
	void write_2(uint32 laddr, uint32 data) { write_n(laddr, 2, data); }
	void write_4(uint32 laddr, uint32 data) { write_n(laddr, 4, data); }

	// MMU アクセス
	virtual busaddr TranslatePeek(busaddr laddr) = 0;
	// 統計
	virtual void CalcStat() = 0;

	// レジスタ
	uint16 GetIR() const	{ return ir; }
	uint16 GetIR2() const	{ return ir2; }
	// SR を変更する (Human68k から使う)
	void SetSR(uint16);
	uint16 GetSR() const	{ return reg.GetSR(); }

 protected:
	// リセット例外コールバック
	void ResetCallback(Event *);

	// 命令実行系コールバック
	void ExecShort(Event *);
	void ExecFull(Event *);
	void ExecTrace(Event *);

	// トレース設定。デバッガから呼び出される。
	void SetTrace(bool enable) override;

	// 割り込みイベントコールバック
	void InterruptCallback(Event *);

	// 次の実行サイクルを Full にする。
	void MakeNextFull();

	inline void CYCLE(int cc) {
		used_cycle += cc;
	}
	// 副作用前提のマクロなので do-while にしない。
#define CYCLE3(name)	used_cycle += cycle_table[__CONCAT(cycidx_,name)]

	// PC を addr に変更する。
	// ベクタへのジャンプはここではなく JumpVector()。
	inline void Jump(uint32 addr) {
		// 先にブランチ履歴に記録
		brhist.AddEntry(ppc | (IsSuper() ? 1 : 0), addr, ir);

		if (__predict_false((addr & 1) != 0)) {
			ExceptionBuserr(M68K::EXCEP_ADDRERR);
			return;
		}
		reg.pc = addr;
	}

	// -(An) 実行前にレジスタを保存する。
	// バスエラーからの復帰用。
	inline void save_reg_pd(int n) {
		// 該当ビットのフラグを立てる
		flag_pd |= (1U << n);

		// 保存
		save_pd[n] = reg.A[n];
	}

	// (An)+ 実行前にレジスタを保存する。
	// バスエラーからの復帰用。
	inline void save_reg_pi(int n) {
		// 該当ビットのフラグを立てる
		flag_pi |= (1U << n);

		// 保存
		save_pi[n] = reg.A[n];
	}

	// -(An) 実行前にレジスタを(まだ保存してなければ)保存する。
	// 1命令で同じレジスタを2回 -(An) する可能性がある命令用。
	inline void save_reg_pd_if(int n) {
		if ((flag_pd & (1U << n)) == 0) {
			save_reg_pd(n);
		}
	}
	// (An)+ 実行前にレジスタを(まだ保存してなければ)保存する。
	// 1命令で同じレジスタを2回 (An)+ する可能性がある命令用。
	inline void save_reg_pi_if(int n) {
		if ((flag_pi & (1U << n)) == 0) {
			save_reg_pi(n);
		}
	}

	void restore_reg_pi();
	void restore_reg_pd();
	void JumpVector(uint vector, bool from_buserr);

 protected:
#include "m680x0ea.h"

 protected:
	// 基本アクセスルーチンを使った便利サブルーチン
	inline void push_2(uint32 data) {
		reg.A[7] -= 2;
		write_2(reg.A[7], data);
	}
	inline void push_4(uint32 data) {
		reg.A[7] -= 4;
		write_4(reg.A[7], data);
	}
	inline uint32 pop_2() {
		uint32 data = read_2(reg.A[7]);
		reg.A[7] += 2;
		return data;
	}
	inline uint32 pop_4() {
		uint32 data = read_4(reg.A[7]);
		reg.A[7] += 4;
		return data;
	}

	// バスアクセス
	virtual uint32 read_data(busaddr addr) = 0;
	virtual void write_data(busaddr addr, uint32 data) = 0;

	// 物理バスアクセス
	busdata read_phys(const busaddr);
	busdata write_phys(const busaddr, uint32 data);
	bool read_phys_burst(busaddr, uint32 *dst);
	busdata read_phys_4(uint32 addr);
	busdata write_phys_4(uint32 addr, uint32 data);

#define OP_PROTO(name)	void __CONCAT(op_,name)()
#include "m680x0ops.h"
#undef OP_PROTO

	void ops_divu_32_32(uint r, uint q);
	void ops_divu_64_32(uint r, uint q);
	void ops_divs_32_32(uint r, uint q);
	void ops_divs_64_32(uint r, uint q);
	void ops_link(int32 imm);
	void ops_movec_rc_rn();
	void ops_movec_rn_rc();
	virtual bool ops_movec_rc_rn_cpu(uint c, uint n) = 0;
	virtual bool ops_movec_rn_rc_cpu(uint c, uint n) = 0;
	void ops_movep();
	// もともとソース内のテンプレートだった
	void ops_movem_list_ea(int bytesize);
	void ops_movem_ea_list(int bytesize);
	void ops_trapcc(uint cond);
	inline void ops_bra();
	busaddr translate_fc40(busaddr);

	void ops_rte();
	void ops_stop();
	void ops_reset();
	virtual void ops_mmu30() = 0;
	virtual void ops_mmu40_pflush() = 0;

	void ResetFPU(bool hard);
	void fpu_init();
	uint32 cea_fpu(int bytes);
	uint32 cea_fpulc(int bytes);
	void read_8(uint32 addr, uint32 *data);
	void read_12(uint32 addr, uint32 *data);
	void write_8(uint32 addr, const uint32 *data);
	void write_12(uint32 addr, const uint32 *data);

	void fpu_op_illg();
	void fpu_op_illg2();

	void fpu_op_fgen_reg();
	void fpu_op_fgen_mem();
	void fgen(const uint32 *srcdata, uint srcsize_id);
	void fgen881pre(uint dstnum, const uint32 *srcdata, uint srcsize_id);
	bool fgen040pre(uint opmode, uint dst, const uint32 *src, uint srcsize);
	void fpu_op_fmovecr();
	void fpu_op_fmove_to_mem();
	void fpu_op_fmove_b_mem();
	void fpu_op_fmove_w_mem();
	void fpu_op_fmove_l_mem();
	void fpu_op_fmove_s_mem();
	void fpu_op_fmove_d_mem();
	void fpu_op_fmove_x_mem();
	void fpu_op_fmove_p_mem();
	void fpu_op_fmove_ea2ctl();
	void fpu_op_fmovem_ea2ctl();
	void fpu_op_fmove_ctl2ea();
	void fpu_op_fmovem_ctl2ea();
	void fpu_op_fmovem_ea2reg();
	void fpu_op_fmovem_reg2ea();
	void fpu_op_fscc();
	void fpu_op_fdbcc();
	void fpu_op_ftrapcc_w();
	void fpu_op_ftrapcc_l();
	void fpu_op_ftrapcc();
	void fpu_op_fbcc_w();
	void fpu_op_fbcc_l();
	void fpu_op_fsave();
	void fpu_op_frestore();
	void fpu_op_fsave_frame_noimpl(std::array<uint32, 13>&);

	static struct fpframe initial_fpframe;
	static const uint32 fsave_frame_null[1];
	static const uint32 fsave_frame_idle_68881[7];
	static const uint32 fsave_frame_idle_68040[1];

	// キャッシュ
	virtual void SetCACR(uint32) = 0;

	// 例外
	void GenerateExframe0(uint vector, uint16 sr, uint32 pc);
	void GenerateExframe2(uint vector, uint16 sr, uint32 pc, uint32 addr);
	void GenerateExframe4(uint vector, uint16 sr, uint32 ea);
	void GenerateExframe7(uint vector, uint16 sr);
	void GenerateExframeB(uint vector, uint16 sr);
	int  ExceptionReset();
	void ExceptionBuserr(uint vector);
	uint64 ExceptionInterrupt();
	void ExceptionTrap0();
	void ExceptionTrap15();
	void ExceptionFLine();
	void ExceptionFP(uint vector);
	void ExceptionFPLC(uint ea);
	void Exception(uint vector);
	void ExceptionGeneric(uint ext_vector, uint32 info);

	// リセット例外の CPU 固有部分。
	virtual void ResetCache() = 0;
	virtual void ResetMMU() = 0;

	// 二重バスフォールト
	void DoubleBusFault(const char *where);

 public:
	// レジスタ
	m68kreg reg {};
	bool mmu_enable {};		// MMU が有効なら true

	// バス
	m68kbus bus {};
	// 現在の特権状態でのプログラム空間とデータ空間アクセス用。
	busaddr fc_inst {};		// こっちは R 含む
	busaddr fc_data {};		// こっちは R/W 含まない

 protected:
	uint16 ir {};
	uint16 ir2 {};
	// 現在実行中の命令の先頭アドレス
	uint32 ppc {};

	// サイクル数表
	const int8 *cycle_table {};
	static const int8 cycle_table_030cc[];
	static const int8 cycle_table_030ncc[];
	static const int8 cycle_table_040[];

	// レジスタ(状態保存用)
	uint32 save_pd[8] {};	// -(An) を保存
	uint32 save_pi[8] {};	// (An)+ を保存
	uint8  flag_pd {};		// -(An) のどのレジスタを保存したか
	uint8  flag_pi {};		// (An)+ のどのレジスタを保存したか

	// FPU
	enum {
		FPU_STATE_NULL,		// 内部状態なし
		FPU_STATE_IDLE,		// FRESTORE 以降に FPU 命令を実行した
		FPU_STATE_BUSY,		// ビジー状態 (実装していない)
		FPU_STATE_NOIMPL,	// 浮動小数点未実装命令
	} fpu_state {};
	struct fpemu fe {};

	// 68040 FPU 用の内部状態
	struct FPU40 {
		uint32 reg1b;		// CMDREG1B
		uint32 ea;			// 計算した EA
		enum {
			// NONE は未決定だが Unnormal/Denormal ではないいずれかなので
			// UNNORMAL より小さい値にしておく。(大小比較する)
			TAG_NONE		= -1,
			TAG_NORMAL		= 0,
			TAG_ZERO		= 1,
			TAG_INF			= 2,
			TAG_NAN			= 3,
			TAG_UNNORMAL	= 4,	// .X denormal or unnormal
			TAG_DENORMAL	= 5,	// .S/.D denormal

			// 内部表現 (下位3ビットは維持したまま上位で詳細を区別する)
			TAG_DENORMAL_S	= (0x10 | TAG_DENORMAL),
			TAG_DENORMAL_D	= (0x20 | TAG_DENORMAL),
			TAG_DENORMAL_X	= (0x30 | TAG_UNNORMAL),
			TAG_UNNORMAL_X	= (0x40 | TAG_UNNORMAL),
			// PACKED のときタグは undefined となっている。仕方ないので 7。
			TAG_PACKED		= 0x07 ,
		};
		int32 stag;
		int32 dtag;
		uint32 etemp[3];
		uint32 fptemp[3];
		enum {
			E1 = 0x04000000,
			E3 = 0x02000000,
		};
		uint32 eflag;
		bool post;			// T bit, post-op exception

		// 表示用のフラグ。例外発生から FSAVE の間までが有効。
		bool enable;
	} fpu40 {};

	// 割り込みペンディング
	bool intr_pending {};

	// 現在の外部割り込みレベル
	int intr_level {};

	// 電源オンからの累積消費サイクル
	uint64 used_cycle {};

	// 今回のバスウェイト [nsec]
	uint64 buswait {};

	// 外部バスがバースト転送に対応していれば true
	bool has_burst_access {};

	// MPU/FPU 種別
	m680x0MPUType mpu_type {};
	m680x0FPUType fpu_type {};
	const char *mpu_name {};

	// A-Line, F-Line 命令エミュレーションのコールバックとユーザ引数。
	// ユーザ引数には関与しないので呼び出し側が自由に指定してよい。
	// コールバック側で処理完了したら true を返す。
	// デフォルトの処理(すなわちm68k例外)にする場合は false を返す。
	bool (*aline_callback)(MPU680x0Device *, void *) {};
	void *aline_arg {};
	bool (*fline_callback)(MPU680x0Device *, void *) {};
	void *fline_arg {};

	// ブランチ履歴 (例外履歴を含む)
	BranchHistory_m680x0 brhist { OBJ_MPU_BRHIST };
	// 例外履歴のみ
	BranchHistory_m680x0 exhist { OBJ_MPU_EXHIST };

	// NetBSD カーネルのエントリポイント。
	// TRAP#0 の履歴でシステムコールを判定するのに使う。
	uint32 netbsd_entrypoint {};

	// 割り込みイベント
	Event *intr_event {};

	// トレースでコールバックを差し替えることができるように
	// イベントコールバックを変数に入れてある
	EventCallback_t exec_short {};
	EventCallback_t exec_full {};

	// モニター
	void MonitorScreenRegCommon(TextScreen&, m68kreg&, uint32, uint32);
	int  MonitorScreenFPU(TextScreen&, int y, m68kreg&);
	Monitor *reg_monitor {};
	Monitor *atc_monitor {};
	Monitor *cache_monitor {};

	InterruptDevice *interruptdev {};
	VectorTable *vectortable {};
};

class MPU68030Device : public MPU680x0Device
{
	using inherited = MPU680x0Device;

 public:
	MPU68030Device();
	~MPU68030Device() override;

	void SetLogLevel(int loglevel_) override;
	void ResetHard(bool poweron) override;

	uint64 GetSRP()  const { return reg.srp.q; }
	uint32 GetSRPh() const { return reg.srp.h; }
	uint32 GetSRPl() const { return reg.srp.l; }
	uint64 GetCRP()  const { return reg.crp.q; }
	uint32 GetCRPh() const { return reg.crp.h; }
	uint32 GetCRPl() const { return reg.crp.l; }
	uint64 GetTT(int idx) const { return reg.tt[idx]; }
	uint32 GetTC() const { return reg.tc; }
	uint16 GetMMUSR() const { return reg.mmusr; }

	busaddr TranslatePeek(busaddr laddr) override;
	void CalcStat() override;

	// fc に対する文字列("UD"とか)を返す。
	static const char *FCstr(uint fc_) {
		return &fcstr[fc_ * 4];
	}

 private:
	// リセット
	void ResetCache() override;
	void ResetMMU() override;

	// 命令
	bool ops_movec_rc_rn_cpu(uint c, uint n) override;
	bool ops_movec_rn_rc_cpu(uint c, uint n) override;
	void ops_mmu30() override;
	void ops_mmu40_pflush() override;
	uint32 get_fc();
	void ops_mmu30_illg2();
	void ops_mmu30_pflusha();
	void ops_mmu30_pflush();
	void ops_mmu30_pflush_ea();
	void ops_mmu30_pload();
	void ops_mmu30_ptest();
	uint32 ptest_atc(const busaddr);
	uint32 ptest_search(uint level, const busaddr, uint rn);

	// バスアクセス
	uint32 fetch_2() override;
	uint32 fetch_4() override;
	uint32 read_data(busaddr addr) override;
	busdata read_nocache(busaddr addr);
	void write_data(busaddr addr, uint32 data) override;

	busdata read_cache(m68030Cache *cache, busaddr addr);
	busdata read_single(busaddr);
	busdata fill_single(busaddr, busdata);
	busdata write_bus(busaddr addr, uint32 data);

	// キャッシュ
	void SetCACR(uint32) override;

	// MMU
	busaddr TranslateRead(const busaddr laddr);
	busaddr TranslateWrite(const busaddr laddr);
	busaddr TTMatch(const busaddr laddr) const;
	void PageTableSearch(const busaddr laddr, m68030ATCLine *);

	bool SetSRP(uint32, uint32);
	bool SetCRP(uint32, uint32);
	void SetTT(int idx, uint32);
	bool SetTC(uint32);
	void SetMMUSR(uint16);
	// MMU 有効状態を更新。(TC と TT の変更で呼ばれる)
	void mmu_enable_changed();

 public:
	// MMU work
	m68030TT mmu_tt[2] /*{}*/;
	m68030TC mmu_tc /*{}*/;
	m68030ATC atc {this};

 private:
	DECLARE_MONITOR_SCREEN(MonitorScreenReg);
	DECLARE_MONITOR_SCREEN(MonitorScreenATC);
	DECLARE_MONITOR_SCREEN(MonitorScreenCache);
	int MonitorScreenMMU(TextScreen&, int, m68kreg&);
	int MonitorScreenCache1(TextScreen&, int y, m68030Cache *,
		char (*)(const m68030CacheLine&));

	// キャッシュ
	std::unique_ptr<m68030Cache> icache /*{}*/;
	std::unique_ptr<m68030Cache> dcache /*{}*/;

	static const char fcstr[];
};

class MPU68040Device : public MPU680x0Device
{
	using inherited = MPU680x0Device;
	friend class WXPageTable68040Window;

 public:
	MPU68040Device();
	~MPU68040Device() override;

	uint32 GetITT(int n) const	{ return reg.itt[n]; }
	uint32 GetDTT(int n) const	{ return reg.dtt[n]; }
	uint32 GetSRP()		const	{ return reg.srp40; }
	uint32 GetURP()		const	{ return reg.urp40; }
	uint32 GetTC()		const	{ return reg.tc40; }
	uint32 GetMMUSR()	const	{ return reg.mmusr40; }

	busaddr TranslatePeek(busaddr laddr) override;
	void CalcStat() override;

	// デバッガ (pf コマンド) からも使う。
	static std::string GetReg1BName(uint32 opclass, uint32 sz, uint32 cmd);
	static std::string GetFPUTagName(int32);

 private:
	// リセット
	void ResetCache() override;
	void ResetMMU() override;

	// 命令
	bool ops_movec_rc_rn_cpu(uint c, uint n) override;
	bool ops_movec_rn_rc_cpu(uint c, uint n) override;
	void ops_mmu30() override;
	void ops_mmu40_pflush() override;

	// バスアクセス
	uint32 fetch_2() override;
	uint32 fetch_4() override;
	uint32 read_data(busaddr addr) override;
	busdata read_nocache(busaddr addr);
	void write_data(busaddr addr, uint32 data) override;

	// キャッシュ
	void SetCACR(uint32) override;

	// MMU
	busaddr TranslateRead(const busaddr laddr);
	busaddr TranslateWrite(const busaddr laddr);
	m68040TT *TTMatch(std::array<std::unique_ptr<m68040TT>, 2>&,
		const busaddr) const;
	busaddr LookupATC(m68040ATC&, const busaddr);
	void Search(m68040ATCEntry *, const busaddr);
	uint32 PeekDesc(uint32 base, uint32 idx);

	void SetTT(m68040TT *, uint32);
	void SetSRP(uint32);
	void SetURP(uint32);
	void SetTC(uint32);
	// MMU 有効状態を更新。(TC と *TTn の変更で呼ばれる)
	void mmu_enable_changed();

	DECLARE_MONITOR_SCREEN(MonitorScreenReg);
	DECLARE_MONITOR_SCREEN(MonitorScreenATC);
	int MonitorScreenMMU(TextScreen&, int, m68kreg&);
	void MonitorScreenTT(TextScreen&, int x, int y, const char *, uint32);
	void MonitorScreenATC1(TextScreen&, int, bool, const m68040ATC *);

	// FPU
	void MonitorScreenFPU40(TextScreen&, int y, const FPU40&);

	std::array<std::unique_ptr<m68040TT>, 2> mmu_itt /*{}*/;
	std::array<std::unique_ptr<m68040TT>, 2> mmu_dtt /*{}*/;
	std::unique_ptr<m68040TC> mmu_tc /*{}*/;

	std::unique_ptr<m68040ATC> atc_inst /*{}*/;
	std::unique_ptr<m68040ATC> atc_data /*{}*/;
};

// ブートストラップ
extern MPU680x0Device *NewMPU680x0Device();

// これを呼びたい人は mpu を持ってるはず。
inline MPU680x0Device *GetMPU680x0Device(Device *mpu_) {
	return dynamic_cast<MPU680x0Device*>(mpu_);
}

unix.superglobalmegacorp.com

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