OXYGENHUB
◆ main openmsx / src / cpu / MSXCPUInterface.hh
19.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef MSXCPUINTERFACE_HH
2
#define MSXCPUINTERFACE_HH
3
4
#include "BreakPoint.hh"
5
#include "CacheLine.hh"
6
#include "DebugCondition.hh"
7
#include "WatchPoint.hh"
8
9
#include "InfoTopic.hh"
10
#include "MSXDevice.hh"
11
#include "ProfileCounters.hh"
12
#include "SimpleDebuggable.hh"
13
14
#include "narrow.hh"
15
16
#include <array>
17
#include <bitset>
18
#include <concepts>
19
#include <cstdint>
20
#include <memory>
21
#include <vector>
22
23
namespace openmsx {
24
25
class BooleanSetting;
26
class BreakPoint;
27
class CliComm;
28
class DummyDevice;
29
class MSXCPU;
30
class MSXMotherBoard;
31
class VDPIODelay;
32
33
inline constexpr bool PROFILE_CACHELINES = false;
34
enum class CacheLineCounters : uint8_t {
35
	NonCachedRead,
36
	NonCachedWrite,
37
	GetReadCacheLine,
38
	GetWriteCacheLine,
39
	SlowRead,
40
	SlowWrite,
41
	DisallowCacheRead,
42
	DisallowCacheWrite,
43
	InvalidateAllSlots,
44
	InvalidateReadWrite,
45
	InvalidateRead,
46
	InvalidateWrite,
47
	FillReadWrite,
48
	FillRead,
49
	FillWrite,
50
	NUM // must be last
51
};
52
std::ostream& operator<<(std::ostream& os, EnumTypeName<CacheLineCounters>);
53
std::ostream& operator<<(std::ostream& os, EnumValueName<CacheLineCounters> evn);
54
55
class MSXCPUInterface : public ProfileCounters<PROFILE_CACHELINES, CacheLineCounters>
56
{
57
public:
58
	explicit MSXCPUInterface(MSXMotherBoard& motherBoard);
59
	MSXCPUInterface(const MSXCPUInterface&) = delete;
60
	MSXCPUInterface(MSXCPUInterface&&) = delete;
61
	MSXCPUInterface& operator=(const MSXCPUInterface&) = delete;
62
	MSXCPUInterface& operator=(MSXCPUInterface&&) = delete;
63
	~MSXCPUInterface();
64
65
	/**
66
	 * Devices can register their In ports. This is normally done
67
	 * in their constructor. Once device are registered, their
68
	 * readIO() method can get called.
69
	 */
70
	void register_IO_In(uint8_t port, MSXDevice* device);
71
	void unregister_IO_In(uint8_t port, MSXDevice* device);
72
73
	/**
74
	 * Devices can register their Out ports. This is normally done
75
	 * in their constructor. Once device are registered, their
76
	 * writeIO() method can get called.
77
	 */
78
	void register_IO_Out(uint8_t port, MSXDevice* device);
79
	void unregister_IO_Out(uint8_t port, MSXDevice* device);
80
81
	/** Convenience methods for {un}register_IO_{In,Out}.
82
	 * At the same time (un)register both In and Out, and/or a range of ports.
83
	 */
84
	void register_IO_InOut(uint8_t port, MSXDevice* device);
85
	void register_IO_In_range(uint8_t port, unsigned num, MSXDevice* device);
86
	void register_IO_Out_range(uint8_t port, unsigned num, MSXDevice* device);
87
	void register_IO_InOut_range(uint8_t port, unsigned num, MSXDevice* device);
88
	void unregister_IO_InOut(uint8_t port, MSXDevice* device);
89
	void unregister_IO_In_range(uint8_t port, unsigned num, MSXDevice* device);
90
	void unregister_IO_Out_range(uint8_t port, unsigned num, MSXDevice* device);
91
	void unregister_IO_InOut_range(uint8_t port, unsigned num, MSXDevice* device);
92
93
	/**
94
	 * These methods replace a previously registered device with a new one.
95
	 *
96
	 * This method checks whether the current device is the same as the
97
	 * 'oldDevice' parameter.
98
	 * - If it's the same, the current device is replace with 'newDevice'
99
	 *   and this method returns true.
100
	 * - If it's not the same, then no changes are made and this method
101
	 *   returns false.
102
	 *
103
	 * The intention is that devices using these methods extend (=wrap) the
104
	 * functionality of a previously registered device. Typically the
105
	 * destructor of the wrapping device will perform the inverse
106
	 * replacement.
107
	 */
108
	bool replace_IO_In (uint8_t port, MSXDevice* oldDevice, MSXDevice* newDevice);
109
	bool replace_IO_Out(uint8_t port, MSXDevice* oldDevice, MSXDevice* newDevice);
110
111
	/**
112
	 * Devices can register themself in the MSX slot structure.
113
	 * This is normally done in their constructor. Once devices
114
	 * are registered their readMem() / writeMem() methods can
115
	 * get called.
116
	 */
117
	void registerMemDevice(MSXDevice& device,
118
	                       int ps, int ss, unsigned base, unsigned size);
119
	void unregisterMemDevice(MSXDevice& device,
120
	                         int ps, int ss, unsigned base, unsigned size);
121
122
	/** (Un)register global writes.
123
	  * @see MSXDevice::globalWrite()
124
	  */
125
	void   registerGlobalWrite(MSXDevice& device, uint16_t address);
126
	void unregisterGlobalWrite(MSXDevice& device, uint16_t address);
127
128
	/** (Un)register global read.
129
	  * @see MSXDevice::globalRead()
130
	  */
131
	void   registerGlobalRead(MSXDevice& device, uint16_t address);
132
	void unregisterGlobalRead(MSXDevice& device, uint16_t address);
133
134
	/**
135
	 * Reset (the slot state)
136
	 */
137
	void reset();
138
139
	/**
140
	 * This reads a byte from the currently selected device
141
	 */
142
	uint8_t readMem(uint16_t address, EmuTime time) {
143
		tick(CacheLineCounters::SlowRead);
144
		if (disallowReadCache[address >> CacheLine::BITS]) [[unlikely]] {
145
			return readMemSlow(address, time);
146
		}
147
		return visibleDevices[address >> 14]->readMem(address, time);
148
	}
149
150
	/**
151
	 * This writes a byte to the currently selected device
152
	 */
153
	void writeMem(uint16_t address, uint8_t value, EmuTime time) {
154
		tick(CacheLineCounters::SlowWrite);
155
		if (disallowWriteCache[address >> CacheLine::BITS]) [[unlikely]] {
156
			writeMemSlow(address, value, time);
157
			return;
158
		}
159
		visibleDevices[address>>14]->writeMem(address, value, time);
160
	}
161
162
	/**
163
	 * This read a byte from the given IO-port
164
	 * @see MSXDevice::readIO()
165
	 */
166
	uint8_t readIO(uint16_t port, EmuTime time) {
167
		return IO_In[port & 0xFF]->readIO(port, time);
168
	}
169
170
	/**
171
	 * This writes a byte to the given IO-port
172
	 * @see MSXDevice::writeIO()
173
	 */
174
	void writeIO(uint16_t port, uint8_t value, EmuTime time) {
175
		IO_Out[port & 0xFF]->writeIO(port, value, time);
176
	}
177
178
	/**
179
	 * Test that the memory in the interval [start, start +
180
	 * CacheLine::SIZE) is cacheable for reading. If it is, a pointer to a
181
	 * buffer containing this interval must be returned. If not, a null
182
	 * pointer must be returned.
183
	 * Cacheable for reading means the data may be read directly
184
	 * from the buffer, thus bypassing the readMem() method, and
185
	 * thus also ignoring EmuTime.
186
	 * The default implementation always returns a null pointer.
187
	 * An interval will never cross a 16KB border.
188
	 * An interval will never contain the address 0xffff.
189
	 */
190
	[[nodiscard]] const uint8_t* getReadCacheLine(uint16_t start) const {
191
		tick(CacheLineCounters::GetReadCacheLine);
192
		if (disallowReadCache[start >> CacheLine::BITS]) [[unlikely]] {
193
			return nullptr;
194
		}
195
		return visibleDevices[start >> 14]->getReadCacheLine(start);
196
	}
197
198
	/**
199
	 * Test that the memory in the interval [start, start +
200
	 * CacheLine::SIZE) is cacheable for writing. If it is, a pointer to a
201
	 * buffer containing this interval must be returned. If not, a null
202
	 * pointer must be returned.
203
	 * Cacheable for writing means the data may be written directly
204
	 * to the buffer, thus bypassing the writeMem() method, and
205
	 * thus also ignoring EmuTime.
206
	 * The default implementation always returns a null pointer.
207
	 * An interval will never cross a 16KB border.
208
	 * An interval will never contain the address 0xffff.
209
	 */
210
	[[nodiscard]] uint8_t* getWriteCacheLine(uint16_t start) {
211
		tick(CacheLineCounters::GetWriteCacheLine);
212
		if (disallowWriteCache[start >> CacheLine::BITS]) [[unlikely]] {
213
			return nullptr;
214
		}
215
		return visibleDevices[start >> 14]->getWriteCacheLine(start);
216
	}
217
218
	/**
219
	 * CPU uses this method to read 'extra' data from the data bus
220
	 * used in interrupt routines. In MSX this returns always 255.
221
	 */
222
	[[nodiscard]] uint8_t readIRQVector() const;
223
224
	/*
225
	 * Should only be used by PPI
226
	 *  TODO: make private / friend
227
	 */
228
	void setPrimarySlots(uint8_t value);
229
230
	/** @see MSXCPU::invalidateRWCache() */
231
	void invalidateRWCache(uint16_t start, unsigned size, int ps, int ss);
232
	void invalidateRCache (uint16_t start, unsigned size, int ps, int ss);
233
	void invalidateWCache (uint16_t start, unsigned size, int ps, int ss);
234
235
	/** @see MSXCPU::fillRWCache() */
236
	void fillRWCache(unsigned start, unsigned size, const uint8_t* rData, uint8_t* wData, int ps, int ss);
237
	void fillRCache (unsigned start, unsigned size, const uint8_t* rData,                 int ps, int ss);
238
	void fillWCache (unsigned start, unsigned size,                       uint8_t* wData, int ps, int ss);
239
240
	/**
241
	 * Peek memory location
242
	 * @see MSXDevice::peekMem()
243
	 */
244
	[[nodiscard]] uint8_t peekMem(uint16_t address, EmuTime time) const;
245
	[[nodiscard]] uint8_t peekSlottedMem(unsigned address, EmuTime time) const;
246
	void peekSlottedMemBlock(unsigned address, std::span<uint8_t> output, EmuTime time) const;
247
	uint8_t readSlottedMem(unsigned address, EmuTime time);
248
	void writeSlottedMem(unsigned address, uint8_t value,
249
	                     EmuTime time);
250
251
	void setExpanded(int ps);
252
	void unsetExpanded(int ps);
253
	void testUnsetExpanded(int ps,
254
		               std::span<const std::unique_ptr<MSXDevice>> allowed) const;
255
	[[nodiscard]] bool isExpanded(int ps) const { return expanded[ps] != 0; }
256
	void changeExpanded(bool newExpanded);
257
258
	[[nodiscard]] auto getPrimarySlot  (int page) const { return primarySlotState[page]; }
259
	[[nodiscard]] auto getSecondarySlot(int page) const { return secondarySlotState[page]; }
260
261
	[[nodiscard]] DummyDevice& getDummyDevice() { return *dummyDevice; }
262
263
	void insertBreakPoint(BreakPoint bp);
264
	void removeBreakPoint(const BreakPoint& bp);
265
	void removeBreakPoint(unsigned id);
266
	using BreakPoints = std::vector<BreakPoint>;
267
	[[nodiscard]] static BreakPoints& getBreakPoints() { return breakPoints; }
268
269
	void setWatchPoint(const std::shared_ptr<WatchPoint>& watchPoint);
270
	void removeWatchPoint(std::shared_ptr<WatchPoint> watchPoint);
271
	void removeWatchPoint(unsigned id);
272
	// note: must be shared_ptr (not unique_ptr), see WatchIO::doReadCallback()
273
	using WatchPoints = std::vector<std::shared_ptr<WatchPoint>>;
274
	[[nodiscard]] const WatchPoints& getWatchPoints() const { return watchPoints; }
275
	[[nodiscard]] WatchPoints& getWatchPoints() { return watchPoints; }
276
277
	// Temporarily unregister and then re-register a watchpoint. E.g.
278
	// because you want to change the type or address, and then it needs to
279
	// be registered in a different way.
280
	struct ScopedChangeWatchpoint {
281
		ScopedChangeWatchpoint(const ScopedChangeWatchpoint&) = delete;
282
		ScopedChangeWatchpoint(ScopedChangeWatchpoint&&) = delete;
283
		ScopedChangeWatchpoint& operator=(const ScopedChangeWatchpoint&) = delete;
284
		ScopedChangeWatchpoint& operator=(ScopedChangeWatchpoint&&) = delete;
285
286
		ScopedChangeWatchpoint(MSXCPUInterface& interface_, std::shared_ptr<WatchPoint> wp_)
287
			: interface(interface_), wp(std::move(wp_)) {
288
			interface.unregisterWatchPoint(*wp);
289
		}
290
		~ScopedChangeWatchpoint() {
291
			interface.registerWatchPoint(*wp);
292
		}
293
	private:
294
		MSXCPUInterface& interface;
295
		std::shared_ptr<WatchPoint> wp;
296
	};
297
	[[nodiscard]] auto getScopedChangeWatchpoint(std::shared_ptr<WatchPoint> wp) {
298
		return ScopedChangeWatchpoint(*this, std::move(wp));
299
	}
300
301
	void setCondition(DebugCondition cond);
302
	void removeCondition(const DebugCondition& cond);
303
	void removeCondition(unsigned id);
304
	using Conditions = std::vector<DebugCondition>;
305
	[[nodiscard]] static Conditions& getConditions() { return conditions; }
306
307
	[[nodiscard]] static bool isBreaked() { return breaked; }
308
	void doBreak();
309
	void doStep();
310
	void doContinue();
311
312
	// breakpoint methods used by CPUCore
313
	[[nodiscard]] static bool anyBreakPoints()
314
	{
315
		return !breakPoints.empty() || !conditions.empty();
316
	}
317
	[[nodiscard]] bool checkBreakPoints(unsigned pc);
318
319
	// cleanup global variables
320
	static void cleanup();
321
322
	// In fast-forward mode, breakpoints, watchpoints and conditions should
323
	// not trigger.
324
	void setFastForward(bool fastForward_) { fastForward = fastForward_; }
325
	[[nodiscard]] bool isFastForward() const { return fastForward; }
326
327
	/** The device in this slot and page. Never returns nullptr: a slot
328
	  * without a device has the DummyDevice. */
329
	[[nodiscard]] MSXDevice* getMSXDevice(int ps, int ss, int page);
330
	[[nodiscard]] MSXDevice* getVisibleMSXDevice(int page) { return visibleDevices[page]; }
331
332
	/** Returns the device registered on the given IO port, looking through
333
	  * the watchpoint and VDP-delay wrapper devices. Returns the
334
	  * DummyDevice when no device is registered on this port.
335
	  */
336
	[[nodiscard]] MSXDevice* getIODevice(uint8_t port, bool isIn) {
337
		return getDevicePtr(port, isIn);
338
	}
339
340
	/**
341
	 * Read a byte from the given IO-port without side effects.
342
	 * @see MSXDevice::peekIO()
343
	 */
344
	[[nodiscard]] uint8_t peekIO(uint16_t port, EmuTime time) const {
345
		return IO_In[port & 0xFF]->peekIO(port, time);
346
	}
347
348
	template<typename Archive>
349
	void serialize(Archive& ar, unsigned version);
350
351
private:
352
	uint8_t readMemSlow(uint16_t address, EmuTime time);
353
	void writeMemSlow(uint16_t address, uint8_t value, EmuTime time);
354
355
	MSXDevice*& getDevicePtr(uint8_t port, bool isIn);
356
357
	void register_IO  (int port, bool isIn,
358
	                   MSXDevice*& devicePtr, MSXDevice* device);
359
	void unregister_IO(MSXDevice*& devicePtr, MSXDevice* device);
360
	void testRegisterSlot(const MSXDevice& device,
361
	                      int ps, int ss, unsigned base, unsigned size);
362
	void registerSlot(MSXDevice& device,
363
	                  int ps, int ss, unsigned base, unsigned size);
364
	void unregisterSlot(MSXDevice& device,
365
	                    int ps, int ss, unsigned base, unsigned size);
366
367
	void registerWatchPoint(WatchPoint& wp);
368
	void unregisterWatchPoint(WatchPoint& wp);
369
370
	void removeAllWatchPoints();
371
	void updateMemWatch(WatchPoint::Type type);
372
	void executeMemWatch(WatchPoint::Type type, unsigned address,
373
	                     unsigned value = ~0u);
374
375
	struct MemoryDebug final : SimpleDebuggable {
376
		explicit MemoryDebug(MSXMotherBoard& motherBoard);
377
		[[nodiscard]] uint8_t read(unsigned address, EmuTime time) override;
378
		void write(unsigned address, uint8_t value, EmuTime time) override;
379
	} memoryDebug;
380
381
	struct SlottedMemoryDebug final : SimpleDebuggable {
382
		explicit SlottedMemoryDebug(MSXMotherBoard& motherBoard);
383
		[[nodiscard]] uint8_t read(unsigned address, EmuTime time) override;
384
		void readBlock(unsigned start, std::span<uint8_t> output) override;
385
		void write(unsigned address, uint8_t value, EmuTime time) override;
386
	} slottedMemoryDebug;
387
388
	struct IODebug final : SimpleDebuggable {
389
		explicit IODebug(MSXMotherBoard& motherBoard);
390
		[[nodiscard]] uint8_t read(unsigned address, EmuTime time) override;
391
		void write(unsigned address, uint8_t value, EmuTime time) override;
392
	} ioDebug;
393
394
	struct SlotInfo final : InfoTopic {
395
		explicit SlotInfo(InfoCommand& machineInfoCommand);
396
		void execute(std::span<const TclObject> tokens,
397
			     TclObject& result) const override;
398
		[[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
399
	} slotInfo;
400
401
	struct SubSlottedInfo final : InfoTopic {
402
		explicit SubSlottedInfo(InfoCommand& machineInfoCommand);
403
		void execute(std::span<const TclObject> tokens,
404
			     TclObject& result) const override;
405
		[[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
406
	} subSlottedInfo;
407
408
	struct ExternalSlotInfo final : InfoTopic {
409
		explicit ExternalSlotInfo(InfoCommand& machineInfoCommand);
410
		void execute(std::span<const TclObject> tokens,
411
			     TclObject& result) const override;
412
		[[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
413
	} externalSlotInfo;
414
415
	struct IOInfo : InfoTopic {
416
		IOInfo(InfoCommand& machineInfoCommand, const char* name);
417
		void helper(std::span<const TclObject> tokens,
418
		            TclObject& result, std::span<MSXDevice*, 256> devices) const;
419
		[[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
420
	protected:
421
		~IOInfo() = default;
422
	};
423
	struct IInfo final : IOInfo {
424
		explicit IInfo(InfoCommand& machineInfoCommand)
425
			: IOInfo(machineInfoCommand, "input_port") {}
426
		void execute(std::span<const TclObject> tokens,
427
		             TclObject& result) const override;
428
	} inputPortInfo;
429
	struct OInfo final : IOInfo {
430
		explicit OInfo(InfoCommand& machineInfoCommand)
431
			: IOInfo(machineInfoCommand, "output_port") {}
432
		void execute(std::span<const TclObject> tokens,
433
		             TclObject& result) const override;
434
	} outputPortInfo;
435
436
	/** Updated visibleDevices for a given page and clears the cache
437
	  * on changes.
438
	  * Should be called whenever PrimarySlotState or SecondarySlotState
439
	  * was modified.
440
	  * @param page page [0..3] to update visibleDevices for.
441
	  */
442
	void updateVisible(uint8_t page);
443
	inline void updateVisible(uint8_t page, uint8_t ps, uint8_t ss);
444
	void setSubSlot(uint8_t primSlot, uint8_t value);
445
446
	std::unique_ptr<DummyDevice> dummyDevice;
447
	MSXCPU& msxcpu;
448
	CliComm& cliComm;
449
	MSXMotherBoard& motherBoard;
450
	BooleanSetting& pauseSetting;
451
452
	std::unique_ptr<VDPIODelay> delayDevice; // can be nullptr
453
454
	std::array<uint8_t, CacheLine::NUM> disallowReadCache;
455
	std::array<uint8_t, CacheLine::NUM> disallowWriteCache;
456
	std::array<std::bitset<CacheLine::SIZE>, CacheLine::NUM> readWatchSet;
457
	std::array<std::bitset<CacheLine::SIZE>, CacheLine::NUM> writeWatchSet;
458
459
	struct GlobalRwInfo {
460
		MSXDevice* device;
461
		uint16_t addr;
462
		[[nodiscard]] constexpr bool operator==(const GlobalRwInfo&) const = default;
463
	};
464
	std::vector<GlobalRwInfo> globalReads;
465
	std::vector<GlobalRwInfo> globalWrites;
466
467
	std::array<MSXDevice*, 256> IO_In;
468
	std::array<MSXDevice*, 256> IO_Out;
469
	std::array<std::array<std::array<MSXDevice*, 4>, 4>, 4> slotLayout;
470
	std::array<MSXDevice*, 4> visibleDevices;
471
	std::array<uint8_t, 4> subSlotRegister;
472
	std::array<uint8_t, 4> primarySlotState;
473
	std::array<uint8_t, 4> secondarySlotState;
474
	uint8_t initialPrimarySlots;
475
	std::array<unsigned, 4> expanded;
476
477
	bool fastForward = false; // no need to serialize
478
479
	//  All CPUs (Z80 and R800) of all MSX machines share this state.
480
	static inline BreakPoints breakPoints; // unsorted
481
	WatchPoints watchPoints; // ordered in creation order,  TODO must also be static
482
	static inline Conditions conditions; // ordered in creation order
483
	static inline bool breaked = false;
484
};
485
486
487
// Compile-Time Interval (half-open).
488
//   TODO possibly move this to utils/
489
template<unsigned BEGIN, unsigned END = BEGIN + 1>
490
struct CT_Interval
491
{
492
	[[nodiscard]] unsigned begin() const { return BEGIN; } // inclusive
493
	[[nodiscard]] unsigned end()   const { return END; }   // exclusive
494
};
495
496
// Execute an 'action' for every element in the given interval(s).
497
inline void foreach_ct_interval(std::invocable<unsigned> auto action, auto ct_interval)
498
{
499
	for (auto i = ct_interval.begin(); i != ct_interval.end(); ++i) {
500
		action(i);
501
	}
502
}
503
inline void foreach_ct_interval(std::invocable<unsigned> auto action,
504
                                auto front_interval, auto... tail_intervals)
505
{
506
	foreach_ct_interval(action, front_interval);
507
	foreach_ct_interval(action, tail_intervals...);
508
}
509
510
511
template<typename MSXDEVICE, typename... CT_INTERVALS>
512
struct GlobalRWHelper
513
{
514
	void execute(std::invocable<MSXCPUInterface&, MSXDevice&, unsigned> auto action)
515
	{
516
		auto& dev = static_cast<MSXDEVICE&>(*this);
517
		auto& cpu = dev.getCPUInterface();
518
		foreach_ct_interval(
519
			[&](unsigned addr) { action(cpu, dev, addr); },
520
			CT_INTERVALS()...);
521
	}
522
};
523
524
template<typename MSXDEVICE, typename... CT_INTERVALS>
525
struct GlobalWriteClient : GlobalRWHelper<MSXDEVICE, CT_INTERVALS...>
526
{
527
	GlobalWriteClient(const GlobalWriteClient&) = delete;
528
	GlobalWriteClient(GlobalWriteClient&&) = delete;
529
	GlobalWriteClient& operator=(const GlobalWriteClient&) = delete;
530
	GlobalWriteClient& operator=(GlobalWriteClient&&) = delete;
531
532
	GlobalWriteClient()
533
	{
534
		this->execute([](MSXCPUInterface& cpu, MSXDevice& dev, unsigned addr) {
535
			cpu.registerGlobalWrite(dev, narrow<uint16_t>(addr));
536
		});
537
	}
538
539
	~GlobalWriteClient()
540
	{
541
		this->execute([](MSXCPUInterface& cpu, MSXDevice& dev, unsigned addr) {
542
			cpu.unregisterGlobalWrite(dev, narrow<uint16_t>(addr));
543
		});
544
	}
545
};
546
547
template<typename MSXDEVICE, typename... CT_INTERVALS>
548
struct GlobalReadClient : GlobalRWHelper<MSXDEVICE, CT_INTERVALS...>
549
{
550
	GlobalReadClient(const GlobalReadClient&) = delete;
551
	GlobalReadClient(GlobalReadClient&&) = delete;
552
	GlobalReadClient& operator=(const GlobalReadClient&) = delete;
553
	GlobalReadClient& operator=(GlobalReadClient&&) = delete;
554
555
	GlobalReadClient()
556
	{
557
		this->execute([](MSXCPUInterface& cpu, MSXDevice& dev, unsigned addr) {
558
			cpu.registerGlobalRead(dev, narrow<uint16_t>(addr));
559
		});
560
	}
561
562
	~GlobalReadClient()
563
	{
564
		this->execute([](MSXCPUInterface& cpu, MSXDevice& dev, unsigned addr) {
565
			cpu.unregisterGlobalRead(dev, narrow<uint16_t>(addr));
566
		});
567
	}
568
};
569
570
} // namespace openmsx
571
572
#endif