OXYGENHUB
◆ main openmsx / src / memory / RomAlAlamiah30in1.cc
7.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
// Al Alamiah 30-in-1 cartridge series
2
//
3
// Wouter's analysis on the schematic that we drew up with the PCB:
4
// * pin /SLTSL of the MSX cartridge slot is used (not /CS1, /CS2 or
5
//   /CS12). This means the device reacts to reads on the whole memory
6
//   region 0x0000..0xffff.
7
// * pin A15 from the MSX cartridge slot does not seem to be connected.
8
//   That means memory region 0x0000-0x7ffff is mirrored in range
9
//   0x8000..0xffff.
10
// * Only pins A7..A3 (not A2..A0) seem to be used in the decoding of the
11
//   IO-port number. That means the cartridge will react to port range
12
//   0..7.
13
// * The 74LS174 can only store 6 bits (not 8). Only data bits D0..D5 of the
14
//   MSX cartridge slot are connected to the 74LS174. That means that the upper
15
//   two bits that are written via an OUT 0-7 operation are ignored.
16
// * The 74LS174 /RESET input is connected to the /RESET signal of the MSX
17
//   cartridge slot. That means that on reset, the output pins Q0..Q5 of the
18
//   74LS174 are set to zero. Thus bank 0 gets selected and (see later) we again
19
//   allow to select a new bank.
20
// * The 74LS174 latches the input on a rising clock-edge. That mean a change
21
//   from 0 to 1 on the CLOCK input pin. See later for how this CLOCK input
22
//   signal gets calculated
23
// * The 74LS02 chips offers 4 NOR gates, it seems only 2 of these are used.
24
// * For one of these two NOR gates both inputs are connected to the same
25
//   signal (A7 of the MSX cartridge slot). This effectively turns this NOR gate
26
//   into a NOT gate.
27
// * The 74LS138 chip is a 3-to-8-decoder. The datasheet(s) I found use a
28
//   different naming convention for the pin names than what is used in this
29
//   schematic. So I'm making some assumptions here:
30
//   * I'm assuming the A0, A1, A2 pins are the address-input pins. These 3
31
//     pins are interpreted as a value (between 0 and 7) and select one of the
32
//     8 output pins.
33
//   * In this schematic only O0 is connected. This output pin becomes active
34
//     (= low) when the inputs A2,A1,A0 are 0,0,0.
35
//   * In addition the 74LS138 has several enable pins, I'm assuming those are
36
//     the E1, E2, E3 pins in the schematic.
37
//   * The decoder is only 'enabled' when two of these enable-inputs are 0 and
38
//     the 3rd is 1. Unfortunately in this schematic it's not clear which of
39
//     E1,E2,E3 plays which role (but I can make an educated guess).
40
//   * The inputs of the decoder are connected to the A7, A6, A5, A4, A3 and
41
//     /IORQ signals of the MSX cartridge slot. We want this device to react on
42
//     "OUT 0-7".
43
//     Thus all these signals (A7..A3 and /IORQ) must be zero. That's indeed the
44
//     case in this schematic: A7 gets inverted (via a NOT gate) and routed to
45
//     the E3 input (so we can assume this is the enable-input that must be 1),
46
//     and all other inputs must be 0 for the output O0 to become 0 (=active)
47
//     (in all other cases the output O0 will be 1).
48
// * The output O0 of the 74LS138 (active on a "OUT 0-7") is routed to the
49
//   input of a NOR gate.
50
// * The other input of this NOR gate is the Q5 output of the 74LS174 (this
51
//   chip remembers the last written value to port 0).
52
// * A NOR gate outputs a 1 when both inputs are 0 (and 0 in all other cases).
53
// * This means the output can only be 1 when we're processing a "OUT 0-7"
54
//   operation (good). And in addition Q5 must (still) be 0.
55
// * The output of the above NOR gate is routed to the CLOCK input of the
56
//   74LS174. We should examine when this signal changes from 0 to 1. Lets do
57
//   this in two steps:
58
//   * First: assume the Q5 output is 0 (this is the case after a reset).
59
//     * Then the signal changes from 0 to 1 on an "OUT 0-7" operation. That's
60
//       indeed what we want.
61
//     * In other words: on an "OUT 0-7" instruction we latch (=remember) the
62
//       bits D5..D0 (and ignore D7..D6) that were written in the OUT operation.
63
//   * Second: assume the Q5 output is 1. This can only happen when a prior
64
//     write has set this bit to 1.
65
//     * In this case the output of the NOR gate will always be 0. In other
66
//       words, it cannot change from 0 to 1 anymore. And then it's impossible
67
//       to change the latched value in the 74LS174.
68
//    * The only way to "recover" is via a RESET (this sets the Q5 output back
69
//      to 0).
70
//
71
// Summary:
72
// * Writing a value 0..31, thus with bit 5 = 0 (and ignore bit 6 and 7) to
73
//   port 0..7 will select the corresponding bank.
74
// * Writing a different value 0..31 selects a different bank.
75
// * Writing a value 32..63, thus bit5 = 1 (ignore bits 6,7) will also select a
76
//   bank, but in addition this will block any further writes (writes to port
77
//   0..7 will have no effect).
78
// * The only way to recover from this is via a RESET. This reset will also
79
//   select bank 0.
80
//
81
// (End analysis by Wouter.)
82
//
83
// Also important to note: the /WR pin is not connected, so the mapper doesn't
84
// know whether we're reading from or writing to it (either I/O or memory)
85
//
86
// Note that on real hardware, there seem to be many cases that block 31 of the
87
// mapper is selected at random. I guess this is because of some noise on the
88
// bus or something... but that must have been one of the reasons to implement
89
// this block locking mechanism. Another reason could be protection against the
90
// software in the blocks writing to ports 0-7. But that should not be very
91
// common.
92
//
93
// Many thanks to tsjakoe for helping to reverse engineer the mapper and
94
// dumping a ROM to test. And of course to Hashem for sending me the Group B
95
// cartridge so we could reverse engineer the mapper in the first place.
96
97
#include "RomAlAlamiah30in1.hh"
98
#include "MSXCPUInterface.hh"
99
#include "serialize.hh"
100
#include "xrange.hh"
101
102
namespace openmsx {
103
104
RomAlAlamiah30in1::RomAlAlamiah30in1(const DeviceConfig& config, Rom&& rom_)
105
	: Rom16kBBlocks(config, std::move(rom_))
106
{
107
	reset(EmuTime::dummy());
108
	getCPUInterface().register_IO_InOut_range(0x00, 8, this);
109
}
110
111
RomAlAlamiah30in1::~RomAlAlamiah30in1()
112
{
113
	getCPUInterface().unregister_IO_InOut_range(0x00, 8, this);
114
}
115
116
void RomAlAlamiah30in1::reset(EmuTime time)
117
{
118
	mapperLocked = false;
119
	writeIO(0, 0, time);
120
}
121
122
void RomAlAlamiah30in1::writeIO(uint16_t /*port*/, byte value, EmuTime /*time*/)
123
{
124
	if (!mapperLocked) {
125
		// setRom works with 16kB pages as we're using Rom16kBBlocks,
126
		// but we deal with 32kB blocks in the ROM image
127
		byte page = 2 * (value & 0b1'1111);
128
		setRom(0, page + 0);
129
		setRom(1, page + 1);
130
		setRom(2, page + 0);
131
		setRom(3, page + 1);
132
	}
133
	// bit 5 of the value sets the mapper to a locked position
134
	mapperLocked = mapperLocked || ((value & 0b10'0000) != 0);
135
}
136
137
byte RomAlAlamiah30in1::readIO(uint16_t port, EmuTime time)
138
{
139
	// as the cartridge doesn't look at the WR signal, assume a write
140
	// of 0xFF (as if there are pull-up resistors on the bus).
141
	writeIO(port, 0xFF, time);
142
	return 0xFF;
143
}
144
145
template<typename Archive>
146
void RomAlAlamiah30in1::serialize(Archive& ar, unsigned /*version*/)
147
{
148
        ar.template serializeBase<Rom16kBBlocks>(*this);
149
        ar.serialize("mapperLocked",  mapperLocked);
150
}
151
INSTANTIATE_SERIALIZE_METHODS(RomAlAlamiah30in1);
152
153
REGISTER_MSXDEVICE(RomAlAlamiah30in1, "RomAlAlamiah30in1");
154
155
} // namespace openmsx