openMSX
ROMHunterMk2.cc
Go to the documentation of this file.
1#include "ROMHunterMk2.hh"
2#include "narrow.hh"
3#include "ranges.hh"
4#include "serialize.hh"
5#include <cassert>
6
7/*
8
9As reverse engineered by BiFi:
10
11At 0x3FFF is a configuration register.
12bit 1: page 1 control:
13 0: ROM hunter ROM in page 1 for READs, writes ignored (default)
14 1: writes go to RAM in page 1, mapper switching disabled
15bits 2 and 0 is a mapper selector:
16 00 = ASCII16 (default),
17 01 = ASCII8,
18 11 = Konami,
19 10 = unknown (seems to be some Konami 16K variant)
20bit 3: RAM write protect:
21 0: write enable (default)
22 1: write protect; also sets reads in page 1 to RAM (and not ROM)
23
24When the ROM loads a MegaROM, bit 1 is left on 0. But for plain ROMs, it is set
25to 1.
26*/
27
28namespace openmsx {
29
31 : MSXRom(config, std::move(rom_))
32 , ram(config, getName() + " RAM", "ROM Hunter Mk 2 RAM", 0x40000)
33{
35}
36
37void ROMHunterMk2::reset(EmuTime::param /*time*/)
38{
39 configReg = 0;
40 ranges::fill(bankRegs, 0);
41 invalidateDeviceRCache(); // flush all to be sure
42}
43
44unsigned ROMHunterMk2::getRamAddr(unsigned addr) const
45{
46 unsigned page = (addr >> 13) - 2;
47 assert(page < 4);
48 unsigned bank = bankRegs[page];
49 return (bank * 0x2000) + (addr & 0x1FFF);
50}
51
52const byte* ROMHunterMk2::getReadCacheLine(word addr) const
53{
54 // reads outside [0x4000, 0xC000) return 0xFF
55 if ((addr < 0x4000) || (0xC000 <= addr)) {
56 return unmappedRead.data();
57 }
58
59 // if bit 3 is 0, reads from page 1 come from the ROM, else from the RAM
60 if (((configReg & 0b1000) == 0) && (addr < 0x8000)) {
61 return &rom[addr & 0x1FFF];
62 } else {
63 return &ram[getRamAddr(addr)];
64 }
65}
66
67byte ROMHunterMk2::peekMem(word addr, EmuTime::param /*time*/) const
68{
69 return *getReadCacheLine(addr);
70}
71
72byte ROMHunterMk2::readMem(word addr, EmuTime::param time)
73{
74 return peekMem(addr, time); // reads have no side effects
75}
76
77void ROMHunterMk2::writeMem(word addr, byte value, EmuTime::param /*time*/)
78{
79 // config register at address 0x3FFF
80 if (addr == 0x3FFF) {
81 configReg = value;
82 invalidateDeviceRCache(0x4000, 0x8000);
83 return;
84 }
85
86 // ignore (other) writes outside [0x4000, 0xC000)
87 if ((addr < 0x4000) || (0xC000 <= addr)) {
88 return;
89 }
90
91 // address is calculated before writes to other regions take effect
92 unsigned ramAddr = getRamAddr(addr);
93
94 // only write mapper registers if bit 1 is not set
95 if ((configReg & 0b10) == 0) {
96 // (possibly) write to bank registers
97 switch (configReg & 0b101) {
98 case 0b000: {
99 // ASCII-16
100 // Implemented similarly as the (much later) MFR SCC+,
101 // TODO did we verify that ROMHunterMk2 behaves the same?
102 //
103 // ASCII-16 uses all 4 bank registers and one bank
104 // switch changes 2 registers at once. This matters
105 // when switching mapper mode, because the content of
106 // the bank registers is unchanged after a switch.
107 const byte maskedValue = value & 0xF;
108 if ((0x6000 <= addr) && (addr < 0x6800)) {
109 bankRegs[0] = narrow_cast<byte>(2 * maskedValue + 0);
110 bankRegs[1] = narrow_cast<byte>(2 * maskedValue + 1);
111 invalidateDeviceRCache(0x4000, 0x4000);
112 }
113 if ((0x7000 <= addr) && (addr < 0x7800)) {
114 bankRegs[2] = narrow_cast<byte>(2 * maskedValue + 0);
115 bankRegs[3] = narrow_cast<byte>(2 * maskedValue + 1);
116 invalidateDeviceRCache(0x8000, 0x4000);
117 }
118 break;
119 }
120 case 0b001:
121 // ASCII-8
122 if ((0x6000 <= addr) && (addr < 0x8000)) {
123 byte bank = (addr >> 11) & 0x03;
124 bankRegs[bank] = value & 0x1F;
125 invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
126 }
127 break;
128 case 0b101:
129 // Konami
130 if ((0x6000 <= addr) && (addr < 0xC000)) {
131 unsigned bank = (addr >> 13) - 2;
132 bankRegs[bank] = value & 0x1F;
133 invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
134 }
135 break;
136 case 0b100:
137 // TODO how does this configuration behave?
138 break;
139 }
140 }
141
142 // write to RAM, if not write-protected
143 if ((configReg & 0b1000) == 0) {
144 // if write to [0x8000, 0xC000), just do it
145 // if write to [0x4000, 0x8000), only do it if bit 1 is set
146 if ((addr >= 0x8000) || ((configReg & 0b10) == 0b10)) {
147 ram[ramAddr] = value;
148 }
149 }
150}
151
153{
154 return nullptr;
155}
156
157template<typename Archive>
158void ROMHunterMk2::serialize(Archive& ar, unsigned /*version*/)
159{
160 // skip MSXRom base class
161 ar.template serializeBase<MSXDevice>(*this);
162 ar.serialize("ram", ram,
163 "configReg", configReg,
164 "bankRegs", bankRegs);
165}
168
169} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
void invalidateDeviceRCache()
Definition MSXDevice.hh:215
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:306
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
byte * getWriteCacheLine(word address) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
ROMHunterMk2(const DeviceConfig &config, Rom &&rom)
const byte * getReadCacheLine(word address) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
void serialize(Archive &ar, unsigned version)
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
void writeMem(word address, byte value, EmuTime::param time) override
Write a given byte to a given location at a certain time to this device.
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
void reset(EmuTime::param time) override
This method is called on reset.
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
STL namespace.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)